Capture the last time code (for a given event type) in a log file

Hi!

I am only now starting to learn how to use Regex to my advantage when creating macros, and have now spent the better part of the evening trying to figuring out a very specific problem: How do I make Regex return only the last match in a long list of numerous matches?

I am searching a log file wanting to capture the time code of the last time this app was launched. (Example of how this log file can be formatet at the bottom of this post.)

I've read this Log file into a variable and I am trying this 'Search using Regular Expression’ to give me the time code of the last time this app was launchd:

The problem is of course that the Regex string (\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+) App starting only returns the timecode in front of the first match in this list, whereas I am interested in the time code in front of the bottom most occurrence of this match. Most of my regex google searches have pointed med towards Negative Lookahead, but I cannot make make sense of this, neither in practice (where to place the ?!) or through my logic understanding from the description of what Negative Lookahead is supposed to do, it does not seem right.

This is without a doubt only a result of my poor understanding of Regex, but I hope that maybe someone here can help me by pointing me in the right direction.

Example log file:
2023-07-07 20:55:31 App Quitting
2023-07-07 20:55:43 App Starting
2023-07-07 20:55:43 Random Data Noise
2023-07-07 20:55:43 Random Data Noise
2023-07-07 20:55:43 Random Data Noise
2023-07-07 20:55:59 App Quitting
2023-07-10 13:26:59 App Starting
2023-07-10 13:27:02 Random Data Noise
2023-07-10 13:27:02 Random Data Noise
2023-07-10 13:27:09 Random Data Noise
2023-07-10 13:27:09 Random Data Noise
2023-08-01 20:03:34 Random Data Noise
2023-08-01 20:03:36 Random Data Noise
2023-08-01 20:03:36 Random Data Noise
2023-08-01 20:03:36 App Quitting

This is how I might approach this:

  1. Read the log file into a KM variable (which you do already).
  2. Use the Filter action (manual:Filters [Keyboard Maestro Wiki] and action:Filter [Keyboard Maestro Wiki]) to reverse the lines in the variable.
  3. Use your existing regex to grab the first match in the now reversed text in the variable.

I can’t give you an example macro to illustrate as I’m away from my Mac, but I hope you get the idea.

2 Likes

Thank you for your reply! Your proposition makes perfect sense and I will try it out tomorrow

Only I kind of can’t believe that this is not a basic task to ask of Regex to handle. As I’d like to learn Regex better I’d also love any further information about if there is any way to get Regex to return only the last match of a list, or not.

Another thing I just realised is that the log file I want to extract the time code from is 77000 lines long(!) So I might also have to do some tests to check out different ways of doing this task, to see which method is taxing my hardware the least. Now I’m thinking I should find a way to read only the last five pages of the log file into the variable or something like that. But it’d be interesting hearing about other ideas on how to approach this problem as well!

You can use the shell command tail to extract the last chunk of a file into a variable. Still away from my desk, so search the forum for “execute shell script using tail” or similar?

2 Likes

Thanks once again! Been reading about the tail command and it seems like it should do allot of what I need.

I am now trying a Execute Shell Script action, 'with save result to variable' to read the last 100 lines into my variable in reverse order using this command:

tail -r -n 100 "/Users/$_user/Library/Logs/Appname/Log_example.log"
(Where Appname and Log_exampe.log is of course swapped out with actual app and file name.)

But with this directory formating I guess I reveal also my incompetence in command line, as I get the KM error message "No such file or directory" even though the directory/file I am pointing it towards very much exists (proven by the fact that I am able to read the entire file into a variable using the same directory).

Where in my attempt of pointing towards the right directory am I going wrong?
Would I first have to point towards the directory in a separat line in the Shell Script?

Well my guess is what doesn’t exist is a user called $_user

If you have a shell environment variable of that name that contains the actual username, then it won’t work since the KM shell is a vanilla shell with a barebones environment. This issue comes up at least weekly so have a look at the Execute a Shell Script action in the wiki action:Execute a Shell Script [Keyboard Maestro Wiki] and/or search the forum for “shell script problem” - there’s plenty of examples!

Also, have a look in the KM engine log file to see what errors are being reported - it might point you to the cause.

1 Like

Swapping out $_user with my actuall user name made the Shell Script return exactly what I needed! And my macro now runs perfectly

Thank you so very much!

I will also read more in the wiki about the Execute a Shell Script action, as I'd very much like to understand it better

1 Like

That’s great to hear but be careful - whenever I get a macro running “perfectly” I usually find all sorts of ways to improve it :rofl:

1 Like

Ain’t that the truth. The epiphanies, normally, happen when in the shower or simply away from the Mac.

1 Like

Mhm, seems like a macro can aways be improved, rebuilt or tidied up better!
I also have these shower epiphanies, haha

1 Like

Regular expressions are extremely powerful and cool but they are often best used in combination with other tools (like with tail here). You could probably engineer a regex that would do what you need, using some of the more advanced features of regex syntax, but it would almost certainly be going the long way around. (If you’re curious, search for “negative lookahead” in a regex manual.)

By the way, one good way to learn regex on a Mac is to install the free version of the text editor app BBEdit. The manual’s chapter on grep search is a very well-written regex tutorial, and the app has lots of helpful features for designing and testing your regular expressions.

1 Like

Very often, the best way to do that is not to use regular expressions. They don't serve well as a default approach.

Trying to solve a problem by using a regular expression often adds additional problems and costs time – not really an advantage at all ...

Your experience is common:

have now spent the better part of the evening trying to figuring out ... (a regex issue rather than the problem itself)

The underlying problem here seems to be something closer to:

Capture the last time code in a log file

and that might have served you better as a thread title – harvesting various ways of solving the problem itself, which appears to be that of:

  • Obtaining the last non-blank line in a log,
  • taking the first 19 characters from that line,
  • and, possibly, converting those characters a (non string) computable date value of some kind.
1 Like

which does not describe the problem.

Better would be:

Capture the last time code in a log file that a specific event type occurred

for which your suggested approach would need to be modified.

2 Likes

Thanks.

So perhaps, for example:

  • Reversing the lines,
  • filtering the reversed lines for those containing a given event name,
  • and taking the first N chars of the first line yielded by that filter.

(where one and two above could be reversed)

1 Like

If you wanted to experiment with native KM actions ( bypassing both regular expressions and shell scripts ) you could, for example, try things like this:

Last time code in log file for given event type.kmmacros (6.8 KB)

1 Like

I am definitely interested in this experiment! As I don’t really know coding it would almost always be my intuitive goal to solve any problem within the native KM actions!

But if I was to use the timecode for a time difference calculation I would still need to load the elements of the TC into separate variables (Y1, M1, D1, H1, Min1, Sec1 (to use a Time()-calculation)), right? For something Regex captures seems like the maybe best tool? (The original full task I set out to solve was to calculate the time difference between the last time the app was quit before being stated up again.)

Thank you for this explanation! Very useful to know that Regex is often best used in combination with other tools, and that it is not necessarily the do-it-all-tool for these kinds of tasks as I thought it would be.
Thank you also for pointing me in the direction of how I could best learn the tool better!

1 Like

One of the good things about using a Keyboard Maestro native action to do this is that it is very clear to see what is going on, especially if the Macro doesn't work as expected or you want to edit at a future date. To get the value for the hour in this case is simple as the positions of those digits will be the same so you can use the same technique @ComplexPoint has shown. Also, you can rename a Keyboard Maestro Action, in effect adding in a note of what that Action is doing.

3 Likes

This solution makes allot of sense!
Only thing is that I have this (mis)conception that more separate KM-actions equates to a more processor taxing more time consuming macro. But I guess there’s no real difference between dividing it up into separate steps like this or not

Consuming your time, or the machine's ?

(Run-time differences will be very small, whereas all the "I have spent the better part of the evening trying to figure out" time is far from insignificant)