Simplifying template for State Sensor Critical (iterate $value?)

We want to simplify the content of emails from alert State Sensor Critical.

Here’s an example of the content with the default template:
Alert for device - State Sensor Critical
Severity: critical
Timestamp: 2019-12-10 16:15:21
Unique-ID: 100
Rule: State Sensor Critical Faults:
#1: sysObjectID = .1.3.6.1.4.1.9.1.1745; sysDescr = Cisco IOS Software,
IOS-XE Software, Catalyst L3 Switch Software (CAT3K_CAA-UNIVERSALK9-M),
Version 03.06.06E RELEASE SOFTWARE (fc1)
Technical Support: Support - Cisco Support and Downloads – Documentation, Tools, Cases - Cisco
Copyright (c) 1986-2016 by Cisco Systems, Inc.
Compiled Sat 17-Dec-; location_id = 5; override_sysLocation = 1; sensor_id =
75; sensor_oid = .1.3.6.1.4.1.9.9.117.1.1.2.1.2.1001; sensor_descr = Switch
1 - Power Supply A Container; state_descr = off (other);
Alert sent to:

To avoid obfuscation, we want to change that to something like:
1 - Power Supply A Container; state_descr = off (other);
Timestamp: 2019-12-10 16:15:21
Rule: State Sensor Critical

We created a custom template:
{{ $alert->title }}
@if ($alert->faults) Faults:
@foreach ($alert->faults as $key => $value)
{{ $key }}: {{ $value[‘string’] }}
@endforeach
@endif
@if ($alert->state == 0) Time elapsed: {{ $alert->elapsed }} @endif
Timestamp: {{ $alert->timestamp }}
Rule: @if ($alert->name) {{ $alert->name }} @else {{ $alert->rule }} @endif

That resulted in:
Alert for device edge1-mgt.iciti.av - State Sensor Critical
Faults:
1: sysObjectID = .1.3.6.1.4.1.14988.1; sysDescr = RouterOS
CCR1009-7G-1C-1S+; override_sysLocation = 1; sensor_id = 102; sensor_oid =
.1.3.6.1.4.1.14988.1.1.3.16.0; sensor_descr = Backup PSU; state_descr =
false; Timestamp: 2019-12-17 12:20:11
Rule: State Sensor Critical

We should be competent to refine that except for line “sysObjectID = .1.3.6.1.4.1.14988.1; sysDescr = RouterOS CCR1009-7G-1C-1S+; override_sysLocation = 1; sensor_id = 102; sensor_oid = .1.3.6.1.4.1.14988.1.1.3.16.0; sensor_descr = Backup PSU; state_descr = false;”

From https://docs.librenms.org/Alerting/Templates:

Faults, Only available on alert ($alert->state != 0), must be iterated in a foreach (@foreach ($alert->faults as $key => $value) @endforeach). Holds all available information about the Fault, accessible in the format $value[‘Column’], for example: $value[‘ifDescr’]. Special field $value[‘string’] has most Identification-information (IDs, Names, Descrs) as single string, this is the equivalent of the default used and must be encased in {{ }}

Presumably the (IDs, Names, Descrs) are device specific so we can not, in general, output only the ones we want such as $value[‘ifDescr’]. So we need to print all values except for the ones we do not want such as sysObjectID, sysDescr, override_sysLocation … .

I cannot imagine any way to do that other than by iterating $value.

Is there a better solution? If not, can $value be iterated?

Best

Charles

Hi
You have to make a template for each type of alert. Then you can display only the colunm that makes sense for a particular alert.
OR
you can put a bunch of “IF” to display only a field if it makes sense. Then you can achieve a perfectly clean and compact alert with all but only the needed informations.
Bye

Thank you, PipoCanaja.

I tried your IF suggestion and found I do not understand Laravel Blade or PHP well enough to succeed without help.

The intended algorithm was:

  • Create wanted_info as empty array
  • Search $value[‘sysDescr’] for Cisco IOS Software
  • If found add sensor_descr and state_descr to wanted_info
  • Search $value[‘sysDescr’] for RouterOS
  • If found add sensor_descr and state_descr to wanted_info
  • As found necessary, search $value[‘sysDescr’] for other device types of interest
  • If found add wanted info names to wanted_info
  • If wanted_info empty
    • Use default template output lines
  • Else
    • Loop over wanted_info and output each $value[]

I failed to implement that, probably because of trying to create variables in a Laravel template rather than in a Laravel controller (I do not want to mess with code installed by LibreNMS).

https://stackoverflow.com/questions/13002626/how-to-set-variables-in-a-laravel-blade-template?rq=1 suggested creating variables in a Laravel template is contrary to how Laravel should be used, has ugly workarounds and has changed with Laravel releases.

Please give me some pointers on how implement the intended algorithm. My current template:

{{ $alert->title }}
@if ($alert->faults) Faults:
  @foreach ($alert->faults as $key => $value)
    @php
      $wanted_info = array();
    @endphp
    @if (preg_match("/Cisco IOS Software/i", $value["sysObjectID"]))
      Matched Cisco IOS Software
      @php
        $wanted_info[] = "sensor_descr"; 
        $wanted_info[] = "state_descr";
      @endphp
    @elseif (preg_match("/RouterOS/i", $value["sysObjectID"]))
      Matched RouterOS
      @php
        $wanted_info[] = "sensor_descr"; 
        $wanted_info[] = "state_descr";
      @endphp
    @endif
    @php
      $max_index = count($wanted_info);
    @endphp
      Max index A: $max_index
      Max index B: {{ $max_index }}
      Max index C: {{ count($wanted_info) }}
    @if (count($wanted_info) == 0))
      @foreach ($alert->faults as $key => $value)
        {{ $key }}: {{ $value['string'] }}
      @endforeach   
    @else
      @for ($i = 0; $i < count($wanted_info); $i++)
        {{ $wanted[i] }}: {{ $value[wanted[i]] }}
      @endfor
    @endif
  @endforeach
@endif
@if ($alert->state == 0) Time elapsed: {{ $alert->elapsed }} @endif
Timestamp: {{ $alert->timestamp }}
Rule: @if ($alert->name) {{ $alert->name }} @else {{ $alert->rule }} @endif

I don’t get why you try to acheive such a complicated structure. For a StateSensor alert, you’ll always want to display state_descr and sensor_descr ?

And for a single alert, you’ll always have only one device involved. So you can check for the type of device before any loop on the faults. You don’t have to check each fault.

Because ignorance made me think it was necessary. I guessed the values which were present depended on what the device manufacturer chose to include in the MIB. Will state_descr and sensor_descr always be present?

As long as you have a State Sensor, yes, those 2 variables are populated. The value may depend on the mib but the presence of the variable depends only of the LibreNMS database structure, so you can count on it.
Moreover, if the variable is not there, you get an empty string, so it is not critical to display a potentially empty variable, wont break anything.

Thanks for staying with me on this, PipoCanaja. With your guidance developed this template:

{{ $alert->title }}
@if ($alert->faults)
@foreach ($alert->faults as $key => $value)
Sensor name: {{ $value[sensor_descr] }}
Sensor state: {{ $value[state_descr] }}
@endforeach
@endif
@if ($alert->state == 0)
Time elapsed: {{ $alert->elapsed }}
@endif
Timestamp: {{ $alert->timestamp }}
Rule: @if ($alert->name) {{ $alert->name }} @else {{ $alert->rule }} @endif
  • Alert title: {{ $alert->hostname }} - sensor detected fault
  • Recovery title: {{ $alert->hostname }} - recovered sensor detected fault

Resulting in these emails:

Subject: <redacted FQDN> - sensor detected fault
Date: Mon, 23 Dec 2019 15:51:02 +0530
From: LibreNMS <<redacted>@gmail.com>
To: LibreNMS <sysmail@<redacted>>

<redacted FQDN> - sensor detected fault
Sensor name: Backup PSU
Sensor state: false
Timestamp: 2019-12-23 15:45:09
Rule:  State Sensor Critical 

and

Subject: <redacted FQDN> - recovered sensor detected fault
Date: Mon, 23 Dec 2019 16:21:02 +0530
From: LibreNMS <<redacted>@gmail.com>
To: LibreNMS <sysmail@<redacted>>

<redacted FQDN> - recovered sensor detected fault
Sensor name: Backup PSU
Sensor state: false
Time elapsed: 30m
Timestamp: 2019-12-23 16:15:09
Rule:  State Sensor Critical

Curious that “Sensor state” is false in both cases.

Would be nice to indent the template’s logic (@if@elseif etc.) without getting the indentation spaces in the output

1 Like

If you switch to HTML display, then the spaces are not significant anymore, so you “solve” the issue :slight_smile: Of course, in text mode, there is nothing you can do, spaces must be significants.

Concerning the “false”, the alert information is the one you display. So the information is not updated, which is the expected behaviour.
But …
You can get the values from the DB with this kind of syntax (this exemple is taken from my “Utilization Template”, which displays the “current” value at the time of the mail generation :

<b>#{{ $key }}.Input Utilisation</b>: {{ \App\Models\Port::find($value['port_id'])->ifInOctets_rate *8 / $value['ifSpeed'] *100 }} % <br>
<b>#{{ $key }}.Output Utilisation</b>: {{ \App\Models\Port::find($value['port_id'])->ifOutOctets_rate *8 / $value['ifSpeed'] *100 }} % <br>

Thanks for confirming my guess that plain text templates’ whitespace goes verbatim into the message.

Regards getting values from the DB, what we have now is good enough for the evaluation phase. I have added it to the production phase TODO list.

I tried to understand PipoCanaja’s example.

Did not find any LibreNMS documentation on it (maybe my bad) so tried studying the code. Found
\App\Models\Port defined in /opt/librenms/database/factories/ModelFactory.php, specifically in

 59 $factory->define(\App\Models\Port::class, function (Faker\Generator $faker) {
 60     return [
 61         'ifIndex'      => $faker->unique()->numberBetween(),
 62         'ifName'       => $faker->text(20),
 63         'ifDescr'      => $faker->text(255),
 64         'ifLastChange' => $faker->unixTime(),
 65     ];
 66 });

Did not find LibreNMS docs on factory (searched for factory site:docs.librenms.org) but did find docs on Laravel Factory but they were all about creating fake data for testing … ?

@CharlesMAtkinson You don’t need to write a model, just use the existing model that would allow you to gather a more up to date data. Multiple models are available for multiple data.
No need to go into ‘/opt/librenms/database/factories/ModelFactory.php’.

Thank you PipoCanaja

The knowledge I am missing is how to use the existing models. I studied your code and I studied the models but I was not able to figure it out.

I have two use cases so far. One is to get the current SensorState as in Simplifying template for State Sensor Critical (iterate $value?) above. The other is off-topic for this thread but closely related (how to get data from the database for a template). It is for a template for Service up/down to get from table services for a specific service_id the service_type

Anybody?

Not being able to create the templates needed to provide information meaningful to operations support is a showstopper for us adopting LibreNMS.

A year and a half too late for you perhaps, but if you’re still using LibreNMS here is my state alert template:

Device Name: {{ $alert->sysName }}
@if ($alert->os != 'ping')
Operating System: {{ LibreNMS\Config::getOsSetting($alert->os, 'text') }} {{ $alert->version }}
@endif
@if ($alert->hardware)
Hardware: {{ $alert->hardware }}
@endif
@if ($alert->location)
Location: {{ $alert->location }}
@endif

{{ $alert->title }}
Severity: {{ $alert->severity }}
Rule: @if ($alert->name)
{{ $alert->name }}
@else
{{ $alert->rule }}
@endif
@if ($alert->state == 0)
Time elapsed: {{ $alert->elapsed }}
@endif
Timestamp: {{ $alert->timestamp }}
Unique-ID: {{ $alert->uid }}

@if ($alert->faults)
Alerting sensors:

@foreach ($alert->faults as $key => $value)
Sensor #{{ $key }}: {{ $value['sensor_descr'] }}
State: {{ $value['state_descr'] }}

@endforeach 
@endif

It’s fairly verbose so you might want to trim it down a bit. It can handle multiple state alerts in the same notification. This template is only for state sensors. For other sensor types (such as temperature) I use a different template.

1 Like