It is currently April 17th, 2021, 1:46 am

Displaying log files

Help with creating, editing & fixing problems with skins
ParadineZero
Posts: 1
Joined: July 3rd, 2016, 2:58 am

Displaying log files

Post by ParadineZero »

I want to display log files on my desktop from multiple sources.
I've been able to get the file displayed but it only shows the stat of the file, how can I get it to auto scroll to the end of the file?
User avatar
jsmorley
Developer
Posts: 21635
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Displaying log files

Post by jsmorley »

Not sure Rainmeter is the best tool for this. First, there is nothing in Rainmeter proper that is gong to "tail" a file. You are going to need to either write some (fairly trivial) Lua, or use some external utility combined with the RunCommand plugin to display the "last N" lines of a file. However, second, since any such effort will involve reading the entire file into some buffer to get to the last lines of the file, and on EVERY update, it will be hugely inefficient. That may be ok on a small file, but if the log file gets large, there is gong to be just enormous amount of CPU use and disk thrashing.

Maybe someone will have some epiphany that I'm not seeing right off, but I'm hesitant to recommend this as by their nature log files tend to get quite large over time.
User avatar
jsmorley
Developer
Posts: 21635
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Displaying log files

Post by jsmorley »

If you want the Lua approach, it's like this:
TailFile_1.0.rmskin
TailFle.ini

Code: Select all

[Rainmeter]
Update=1000
DynamicWindowSize=1
AccurateText=1
MouseScrollDownAction=[!Refresh]

[Metadata]
Name=TailFile
Author=JSMorley
Information=Example of using Lua to "tail" a number of lines from a text file like a log or to-do list.||Simply set [Variables] to control which file is read, the number of lines, the width of the skin, and the interval in seconds.
License=Creative Commons Attribution-Non-Commercial-Share Alike 3.0
Version=Jan 31, 2015

[Variables]
FileToRead=#@#TailFile.log
LinesToTail=5
SkinWidth=350
SecondsBetweenRead=5
ChildPrefix=MeasureLine

[MeasureTail]
Measure=Script
ScriptFile=TailFile.lua
UpdateDivider=#SecondsBetweenRead#

[MeasureLine1]
Measure=String
UpdateDivider=-1

[MeasureLine2]
Measure=String
UpdateDivider=-1

[MeasureLine3]
Measure=String
UpdateDivider=-1

[MeasureLine4]
Measure=String
UpdateDivider=-1

[MeasureLine5]
Measure=String
UpdateDivider=-1

[StyleText]
X=5
Y=5R
W=(#SkinWidth# - 20)
H=16
FontSize=11
Padding=5,5,5,5
ClipString=1
AntiAlias=1

[StyleOdd]
FontColor=255,255,255,255
SolidColor=40,50,60,255

[StyleEven]
FontColor=230,230,230,255
SolidColor=60,70,80,255

[MeterBack]
Meter=Image
W=(#SkinWidth#)
H=([MeterLine#LinesToTail#:Y]+[MeterLine#LinesToTail#:H]+5)
SolidColor=10,20,30,255
DynamicVariables=1
LeftMouseUpAction=#FileToRead#

[MeterHeaderLeft]
Meter=String
X=5
Y=5
FontSize=11
FontColor=255,255,255,255
StringStyle=Bold
AntiAlias=1
Text=TailFile

[MeterHeaderRight]
Meter=String
X=(#SkinWidth# - 10)
Y=0r
FontSize=11
FontColor=255,255,255,255
StringAlign=Right
AntiAlias=1
Text=#LinesToTail# of [MeasureTail] lines
DynamicVariables=1

[MeterLine1]
Meter=String
MeasureName=MeasureLine1
MeterStyle=StyleText | StyleOdd
Y=10R

[MeterLine2]
Meter=String
MeasureName=MeasureLine2
MeterStyle=StyleText | StyleEven

[MeterLine3]
Meter=String
MeasureName=MeasureLine3
MeterStyle=StyleText | StyleOdd

[MeterLine4]
Meter=String
MeasureName=MeasureLine4
MeterStyle=StyleText | StyleEven

[MeterLine5]
Meter=String
MeasureName=MeasureLine5
MeterStyle=StyleText | StyleOdd
TailFile.lua

Code: Select all

function Initialize()
      
	fileToRead = SKIN:GetVariable('FileToRead')
	linesToTail = SKIN:GetVariable('LinesToTail')
	childPrefix = SKIN:GetVariable('ChildPrefix')

end

function Update()

	local inputFile = io.open(fileToRead, 'r')
	
	local linesTable = {}
	for line in inputFile:lines() do
		table.insert (linesTable, line);
	end
	
	io.close(inputFile)
	
	for i = 1, linesToTail do
		SKIN:Bang('!SetOption', childPrefix..i, 'String', linesTable[(#linesTable + 1) - i])
		SKIN:Bang('!UpdateMeasure', childPrefix..i)
	end
	
	SKIN:Bang('!UpdateMeter', '*')
	SKIN:Bang('!Redraw')	
	
	return #linesTable
	
end
1.jpg
So that causes my CPU usage for Rainmeter to jump from 2% to about 14% for a half-a-second or so every 5 seconds. Granted, that example log file is 450,000 lines long, but as I said, the nature of log files is that they tend to get large over time.

I could probably live with this approach on a single relatively small log file, but it would be a problem if the file gets huge, and compounded if you are reading in multiple log files in multiple skins.

A real "tail" capability would probably need to be a plugin. It would need to open the file in a parent, keep it open, and treat it as a "stream" which it just outputs to child measures. Nothing currently in Rainmeter or even externally is going to be efficient enough for this in my opinion.
You do not have the required permissions to view the files attached to this post.
User avatar
Brian
Developer
Posts: 2114
Joined: November 24th, 2011, 1:42 am
Location: Utah

Re: Displaying log files

Post by Brian »

jsmorley wrote:So that causes my CPU usage for Rainmeter to jump from 2% to about 14% for a half-a-second or so every 5 seconds.
You could probably drop your CPU usage down by starting at the end of the file and searching backwards...something like this:

Code: Select all

function Initialize()
      
	fileToRead = SKIN:GetVariable('FileToRead')
	linesToTail = tonumber(SKIN:GetVariable('LinesToTail'))
	childPrefix = SKIN:GetVariable('ChildPrefix')

end

function Update()
	local inputFile = io.open(fileToRead, 'r')
	local text, ch
	local pos = -1
	local i = 1

	repeat
		inputFile:seek("end", pos - 1)
		ch = inputFile:read(1)
		if ch == '\n' then
			text = inputFile:read(-pos)
			if text ~= nil then
				SKIN:Bang('!SetOption', childPrefix..i, 'String', string.match(text, "^(.-)\n"))
				SKIN:Bang('!UpdateMeasure', childPrefix..i)
				i = i + 1
				pos = pos - 1
			end
		end
		pos = pos - 1
	until (i > linesToTail)

	io.close(inputFile)

	SKIN:Bang('!UpdateMeter', '*')
	SKIN:Bang('!Redraw')

	return 0
end
Of course this way you lose the ability to 'know' how many lines are in the file. I didn't really test this too much, so some modifications probably need to be made.

-Brian
User avatar
jsmorley
Developer
Posts: 21635
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Displaying log files

Post by jsmorley »

That is certainly orders of magnitude more efficient Brian. Really nice.

I'd probably only add one thing:

Code: Select all

function Initialize()
      
	fileToRead = SKIN:GetVariable('FileToRead')
	linesToTail = tonumber(SKIN:GetVariable('LinesToTail'))
	childPrefix = SKIN:GetVariable('ChildPrefix')

end

function Update()
   
	local inputFile = assert(io.open(fileToRead, 'r'))
	local singleChar, lineText
	local bytePosition = -1
	local i = 1

	repeat
		inputFile:seek('end', bytePosition - 1)
		singleChar = inputFile:read(1)
		if singleChar == '\n' then
			lineText = inputFile:read(-bytePosition)
			if lineText then
				lineText = string.gsub(lineText, '\r', '')
				SKIN:Bang('!SetOption', childPrefix..i, 'String', string.match(lineText, '^(.-)\n'))
				SKIN:Bang('!UpdateMeasure', childPrefix..i)
				i = i + 1
				bytePosition = bytePosition - 1
			end
		end
		bytePosition = bytePosition - 1
	until (i > linesToTail)

	io.close(inputFile)

	SKIN:Bang('!UpdateMeter', '*')
	SKIN:Bang('!Redraw')

	return 0

end
The lineText = string.gsub(lineText, '\r', '') is simply to strip off any \r (carriage return) characters from the string you return. While a \r without \n has no meaning in a meter for display purposes, I guess it might impact an IfMatch option or something.

As you can see I also prefer more descriptive variable names, but that is of no real consequence. Mostly just a compulsion to try and help someone else using my code understand what is going on.
User avatar
jsmorley
Developer
Posts: 21635
Joined: April 19th, 2009, 11:02 pm
Location: Fort Hunt, Virginia, USA

Re: Displaying log files

Post by jsmorley »

I have posted a .rmskin of this work by Brian and myself here:

https://forum.rainmeter.net/viewtopic.php?p=108002#p108002
User avatar
Brian
Developer
Posts: 2114
Joined: November 24th, 2011, 1:42 am
Location: Utah

Re: Displaying log files

Post by Brian »

jsmorley wrote:The lineText = string.gsub(lineText, '\r', '') is simply to strip off any \r (carriage return) characters from the string you return. While a \r without \n has no meaning in a meter for display purposes, I guess it might impact an IfMatch option or something.
Thanks for pointing this out. It didn't seem to matter in the context of this, but it is good you pointed it out in case that issue does come up.
jsmorley wrote:As you can see I also prefer more descriptive variable names, but that is of no real consequence. Mostly just a compulsion to try and help someone else using my code understand what is going on.
A good compulsion to have in a support forum like this!

-Brian