Would like new data appended to file

Would someone please help me make this script snippet append the new data to the file (instead of overwriting it):

:local voltage (([/system ups monitor 0 once as-value]->"line-voltage")/100)

:if (([/system/ups/monitor 0 once as-value]->"line-voltage") < 11500) do={
  :log info "UPS input voltage is $voltage"
  :local date [ :system clock get date ]
  :local time [ :system clock get time ]
  /file print file=upsvoltagelow.txt
  :delay 2s
  :file set "upsvoltagelow.txt" contents=""
  :file set "upsvoltagelow.txt"  contents="$date $time $voltage"

Thank you.

Unfortunately, there is no way to append data. Something as basic and simple as output redirection in Linux is not available in RouterOS. You must read the old data first, then write it back, both old and new contents. Watch out for file size limits if you are working with large amount of data.

Looking at your example, it might be a lot easier just to create a logging rule to save this particular data to a separate log file. It will even take care of the timestamps for you.

You can append it pretty simply, you just read the file when you add new data:

/file set "upsvoltagelow.txt"  contents="$[/file get "upsvoltagelow.txt" contents]\n$date $time $voltage"

Now your issue is at some point the file will get too big. Thus, @anserk’s suggest that to do something like /log info "UPS $date $time $voltage" is a better one.

You can just log all script output from /log info "msg" to a file using /system/logging. You can add action to use a file name script (which becomes scriptlog.txt or scriptlog.0.txt), and will have all output from scripts (so not just UPS). In V7, you’d be able to use a regex to find only the UPS ones… but alas I recall your still on V6.

/system logging action add name=scriptlog target=disk disk-file-name="scriptlog"
/system logging add topics=script action=scriptlog

That’s the basic of using logging instead. The benefit is with /log you get plenty of control over the file size, and RouterOS will roll them for you (i.e. so most recent ones are always present, with you controlling how much disk/files to save in /system/logging/action. Merely appending to an existing file, you’ll eventually hit the file size limit and have to manually delete entries or save old file etc.

Yes, the goal is to preserve the UPS voltage data after a reboot.

I had forgotten about the facility to write a log to disk.

So now I set it up and sure enough I’m getting an error.

/system logging action
add disk-file-name=UPSLOG name=diskups target=disk
/system logging
add action=diskups topics=ups

I added the :log line and the script fails to execute.

:local voltage (([/system ups monitor 0 once as-value]->"line-voltage")/100)

:if (([/system/ups/monitor 0 once as-value]->"line-voltage") < 11500) do={
  :log info "UPS input voltage is $voltage"
  :log ups "UPS input voltage is $voltage"

I tried with the topic set up as “script” also.

There is no such topic like ups for :log command. There are only info, warning and error (there is also debug, but I’m not sure about its usage, doesn’t matter). That’s why you have an error. You can’t generate a log message with ups topic by yourself from a script, only RouterOS can add such log messages. Everything, that comes out from a script will appear with script topic in the log. So, this /system logging add action=diskups topics=ups is useless for your script. You need to use regexp to filter out your log messages.

I would suggest you to use '[UPS]: ’ prefix for ALL log messages, that you gonna add from your script, so they will be clearly distinguishable from others.
It will look like that:

:local upsPrefix "[UPS]:"
# ...

:local voltage (([/system ups monitor 0 once as-value]->"line-voltage")/100)

:if ($voltage < 115) do={
  :log info "$upsPrefix UPS input voltage is $voltage"
}
# ...

I also removed double data reading from your code, it looks useless.

So, if your script adds a message, it will appear in the log like this, for example:
[date&time] script,info [UPS]: UPS input voltage is 110

To filter out these messages and put them in a separate log file, you need to add a new action with regexp:

/system logging action add disk-file-name=UPSLOG name=diskups target=disk
/system logging add action=diskups topics=script regex="^\\[UPS\\]:"

It will filter out everything, that begins with ‘[UPS]:’

Wow!

I was so confused.

I thought that the “INFO” in “:log info” was the topic and could be any of the available topics in the dropdown. I still don’t really understand how this works.

For example, I don’t understand how all the many defined topics work.

I understand your elegant solution (basically), and just tried it out and it works beautifully.

I really can’t believe how bad the online manual is. I think the degree of incomprehensibility is invisible to those who understand these things.

Thank you.

I’m not advocating AI, but just as a point of reference to how bad the info out there is:

Well, regarding :log command, MikroTik’s scripting documentation is quite clear :slight_smile:

Other topics are generated by RouterOS, you can’t use them.

AI, well, it’s stupid sometimes. Though, I’m using it to generate regexps, I just can’t handle them with my head :exploding_head:

LOL.

The problem with the documentation is exactly that: The correct info is there, it’s just encrypted by mixing it in among lots and lots of noise.

For all those who think I’m just whining, consider that it would make reasonable sense to go here to get the answer, and find things that sort of, almost, could be useful info, but doesn’t address the entirety of the solution:

No, you are writing script, using the :log command. The documentation for that is here Scripting - RouterOS - MikroTik Documentation.

The page you linked is the documentation for the /log menu.

When you write scripts that use :resolve, do you expect to find the documentation for that under the help page of /ip dns? The answer is obviously “No”.

All seven of them, besides Mikrotik programmers.

I do understand that, but I think you proved my point: To you it makes perfect sense to look in the scripting docs for how to use the :log command. To the masses, we would (incorrectly) think to look in the logging docs.

Well, there is ONLY this info:

log :log write a message to the system log. Available topics are “debug, error, info and warning” :log info “Hello from script”;

Only as an example, the fact that since it comes from a script, “:log info” will come out as “script, info” isn’t written anywhere.

Plenty of confounding things such as that.

But the underlying problem is that if you don’t know what you’re doing, there’s no use in AI…

.

Examples:

If you’ve already done the monitor to get the value, why ask for it again?

:local voltage (([/system ups monitor 0 once as-value]->"line-voltage")/100)

:if (([/system/ups/monitor 0 once as-value]->"line-voltage") < 11500) do={

If it’s a matter of “aesthetics” for the /100, just do it when saving the log.

.

Why write to the file twice?
The first time to set the content to “”
The second time to set the content to “…”

  :file set "upsvoltagelow.txt" contents=""
  :file set "upsvoltagelow.txt"  contents="$date $time $voltage"

You can simply set the content to “”…" just once.

.

Missing check whether file exists.
Use / and not : when are sections and not commands…
Also “monitor 0” is wrong, but I do not have ups to test the correct syntax,
what is the name of “0”?..
NEVER use same variable name date for what value read get date

.

Anyway, you simply need to do this for “simple” file:

/file remove [find where name="upsvoltagelow.txt"]
/file add name="upsvoltagelow.txt" contents=(([/system ups monitor 0 once as-value]->"line-voltage")/100)

and the date and time can be read in the file properties…

Solution, except for “monitor 0”:

:local rawvolt  ([/system ups monitor 0 once as-value]->"line-voltage")
:local theshold 11500
:local voltage  ($rawvolt / 100)
:local cdate    [/system clock get date]
:local ctime    [/system clock get time]
:local filename "upsvoltagelow.txt"

# NAND flash or XOR flash ?
:if ([:len [/file find where name="flash" and type="disk"]] = 1) do={:set filename "/flash/$filename"}

:if ($rawvolt < $theshold) do={
    :log info "UPS input voltage is $voltage"
    :local logline "$cdate $ctime $voltage"
    :local fileid  [/file find where name=$filename]
    :local howmany [:len $fileid]
    :if ($howmany > 0) do={
# if file exist
       :local previouscontent [/file get $fileid contents]
       /file set $fileid contents="$previouscontent\r\n$logline"
    } else={
# if file do not exist
       /file add name=$filename contents=$logline
    }
}

“monitor 0” must be replaced with something like
monitor ([find]->0)
or
monitor [find where name=“upsname”]
but I do not have any UPS to test it.

I can see the student’s review of this professor:

“Great prof, really knows his stuff, eager to help, crazy high standards for his students”

But seriously…

Doesn’t “monitor 0” simply mean the first ups (i.e., the one with id = 0), and if so, what is wrong with calling it that?

The other way (“/system ups monitor SOMEUPSNAME once as-value”) requires the creater of the script to (1) know the name of the UPS, and (2) have to go into the script to manually change if the name of the UPS ever changes.

As far as creating all the local variables (rawvolt, threshold, voltage, cdata, ctime, filename), that is clearly neater, more comprehensible approach. I always get held up by my choice of variable names (using a “-” or other special character; or some form of reseved (or used-elsewhere) name).

Your solution to appending to a file is quite elegant, and no doubt will come in handy for many who read this thread.

I do like, however, the log to disk solution.

Thank you for sharing your expertise.

The issue is described in the first item on the “Scripting Tips and Tricks” documentation page:

Do not use console numbers to get parameter values

Thank you. I found the referenced page.

I know you’re all going to this I’m being dense, but I know that this is far from clear – I think the point of not using id numers is that they can change. But, it shouldn’t be far more work to figure out what is being said than understanding the underlying concept.

Further, is there any merit to my observation that hard coding the UPS name has its own problems?

If you have only one ups and you say its name could change, then using 0 won’t be a problem.

Btw, these docs are quite strange. Why do they say, that the second script will fail? It works fine. What do they mean by “1”? They were using 0 index in the script code and then they write “1”. Is it a typo? Weird.