Simple Syslog client on a Photon

I wrote a simple syslog client for a Photon project I was working on. Short and sweet.

#define FACILITY    1                               // User-level message
#define SEVERITY    6                               // Informational
#define PRIORITY    (FACILITY * 8 + SEVERITY)       // User.Info
#define SYSLOGPORT  514                             // Default syslog port
#define HOSTNAME    "HOST"                          // Change to hostname, not FQDN
UDP Udp;
String buffer = "";
IPAddress remoteIP(10, 0, 0, 202);                  // IP address of syslog server
char months[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

void setup() 
{
    int bytes;
    String logEntry = "Motion Detected!!";
    
    Serial.begin(115200);
    setupTime();
    createMsg(&logEntry);
    delay(3000);
    Serial.println(buffer);
    Serial.println("Length = " + String(buffer.length()));
    Udp.begin(SYSLOGPORT);
    bytes = Udp.sendPacket(buffer, buffer.length(), remoteIP, SYSLOGPORT);
    Serial.println("Number of bytes written = " + String(bytes));
}

void loop()
{

}

void createMsg(String * logMsg)
{
    buffer = "<" + String(PRIORITY) + ">" + months[Time.month() - 1] + " ";
    buffer += String(Time.day()) + " " + Time.format(Time.now(), "%H:%M:%S");
    buffer += " " + String(HOSTNAME) + " log: " + *logMsg;
}

void setupTime(void)
{
    Time.zone(-5);                                  // EST/EDT
    Time.setDSTOffset(1.0);
    if (Time.isDST())
	    Time.beginDST();
    else
	    Time.endDST();
}
1 Like

@syrinxtech, your use of Time.isDST() will not produce the results you expect. That function simply returns true or false depending on where DST is in effect or not from a previous beginDST() or endDSt(). Establishing whether DST is in effect for a specific Photon in a specific timezone needs to be coded. @ScruffR posted this recently:

Another member used the google geolocation library to determine the devices position and then make a call to a website to get the DST offset. I need to find that post...

You may also want to reconsider using Arduino Strings due to their use of dynamic memory allocation. The many calls that will be made to createMsg() will slowly but surely make a mess of the heap memory. Using pre-allocated char buffer coupled with c-string commands will remove the dynamic allocation issue altogether. Nonetheless, thanks for the contribution!

3 Likes

@peekay123, wow, talk about deception in advertising! Thanks for the heads up on the Time.isDST call. At one time I had a very long and complicated way of doing it and replaced it all with what you see thinking that the “smart guys” at Particle had done all the hard work for me. Oh well, live and learn.

And thanks for the comments on the use of String. As an old C programmer I grew up on char[] and not String. I guess I fell for the glamour of so easily building strings, especially when they involve mixing variables of various types into slick print statements. I will rewrite doing it the old fashioned way.

@peekay123, does this look safer?

#define FACILITY    1                               // User-level message
#define SEVERITY    6                               // Informational
#define PRIORITY    (FACILITY * 8 + SEVERITY)       // User.Info
#define SYSLOGPORT  514                             // Default syslog port
#define HOSTNAME    "HOST"                          // Change to hostname, not FQDN
UDP Udp;
IPAddress remoteIP(10, 0, 0, 202);                  // IP address of syslog server
char buffer[512];
char months[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

void setup() 
{
    int bytes;
    char logEntry[] = "Motion Detected!!";
    
    Serial.begin(115200);
    setupTime();
    createMsg(logEntry);
    delay(3000);
    Serial.println(buffer);
    Serial.print("Length = ");
    Serial.println(strlen(logEntry));
    Udp.begin(SYSLOGPORT);
    bytes = Udp.sendPacket(buffer, strlen(buffer), remoteIP, SYSLOGPORT);
    Serial.print("Number of bytes written = ");
    Serial.println(bytes);
}

void loop()
{

}

void createMsg(char * logMsg)
{
    char mmddyy[16];

    Time.setFormat("%H:%M:%S");
    sprintf(mmddyy, "%d:%d:%d", Time.hour(), Time.minute(), Time.second());
    sprintf(buffer, "<%d>%s %d %s %s log: %s", PRIORITY, months[Time.month() - 1], Time.day(), mmddyy, HOSTNAME, logMsg);
}

void setupTime(void)
{
    Time.zone(-5);                                  // EST
    Time.setDSTOffset(1.0);
}

Yup, that's part of the argument here
https://github.com/particle-iot/firmware/issues/1161

@syrinxtech, you can call Time.setFormat() once in setup(). You could also combine your two sprintf() statements into one but that’s just me being efficient. One last thing - by using snprintf() you can ensure that you don’t accidentally blow past your buffer size limit. Otherwise, looks good! :wink:

1 Like

Hey, I’m all about efficiency and doing it right. I totally missed putting setFormat in setup.

I originally had everything in one long string but during a debugging session I broke it into two pieces. Great advice…that’s why I love posting code on here!

1 Like

Here is version 3…

#define FACILITY    1                               // User-level message
#define SEVERITY    6                               // Informational
#define PRIORITY    (FACILITY * 8 + SEVERITY)       // User.Info
#define SYSLOGPORT  514                             // Default syslog port
#define HOSTNAME    "HOST"                          // Change to hostname, not FQDN
UDP Udp;
IPAddress remoteIP(10, 0, 0, 202);                  // IP address of syslog server
char buffer[512];
char months[12][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

void setup() 
{
    int bytes;
    char logEntry[] = "Motion Detected!!";
    
    Serial.begin(115200);
    setupTime();
    createMsg(logEntry);
    delay(3000);
    Serial.println(buffer);
    Serial.print("Length = ");
    Serial.println(strlen(logEntry));
    Udp.begin(SYSLOGPORT);
    bytes = Udp.sendPacket(buffer, strlen(buffer), remoteIP, SYSLOGPORT);
    Serial.print("Number of bytes written = ");
    Serial.println(bytes);
}

void loop()
{

}

void createMsg(char * logMsg)
{
    snprintf(buffer, sizeof(buffer), "<%d>%s %d %02d:%02d:%02d %s log: %s", 
        PRIORITY, months[Time.month() - 1], Time.day(), Time.hour(), Time.minute(), Time.second(), HOSTNAME, logMsg);
}

void setupTime(void)
{
    Time.zone(-5);                                  // EST
    Time.setDSTOffset(1.0);
}
1 Like

This is quite frustrating ScruffR. The Particle folks really need to step up and put something down in code once and for all. Modern OS’s can do it…and don’t say that it’s too complicated or memory hungry.

I made one small change to my code. I added:

Udp.stop();

after the Udp.sendPacket() line of code. Since the connection is so quick, I felt it was better to open, write
and close the connection.

Under the guise of simple error checking, I also added the following lines above the Serial.begin():

if (strchr(logEntry, ':') == NULL)                     // Msg must contain ":"                 
    {
        DEBUGln(F("Invalid msg, does not contain ':'."));
        return;
    }

The initial syslog RFC requires a “:” to separate two parts of the message. Note that this program does not format according to the updated syslog RFC. But, it does work with all of the syslog servers I’ve tried so far, so there is some backwards compatibility.

1 Like

Even though the syslog server I’m using, Kiwi, doesn’t support the RFC5424 style of syslog messages, I went ahead and updated my code to support both RFC 3164 and 5424. There is an enum that you can to pick which version you want and add a few variables for the 5424 version. Now I’m going to look and see if any of the Mac or Linux implementations of the syslog server support RFC5424 so I can test.

Here is the new code:

#ifndef SYSLOG_H
#define SYSLOG_H
//
//  Last update:  02/11/18
//
/****************************************************/
/*  Enums                                           */
/****************************************************/
enum Severity {
    Emergency,          // emerg
    Alert,              // alert
    Critical,           // crit
    Error,              // err
    Warning,            // warning
    Notice,             // notice
    Informational,      // info
    Debug               // debug
};

enum RFCSupport {
    RFC3164,
    RFC5424
};

/****************************************************/
/*  Defines                                         */
/****************************************************/
#define FACILITY        16                                  // Local0
#define SEVERITY        Informational                       // Informational
#define PRIORITY        (FACILITY * 8 + SEVERITY)           // Local0.Info
#define HOSTNAME        "ENVDR"                             // Change to hostname, not FQDN
#define VERSION         1                                   // Current syslog version
#define APPNAME         "-"                                 // APPNAME
#define TZOFFSET        "05:00"                             // Timezone offset (EST)
#define MSGID           "-"                                 // MSGID
#define STRUCTDATA      "-"                                 // Structured data
#define SYSLOG_EN                                           // Enable syslog
#ifdef  SYSLOG_EN
#define SYSLOG(input1,input2)   sendSyslogMsg(input1,input2)
#else
#define SYSLOG(input1, input2)
#endif

/****************************************************/
/*  sendSyslogMsg                                   */
/*  Input                                           */
/*      syslogMsg - string containing syslog msg    */
/*  Output                                          */
/*      0 - Unsupported RFC version                 */
/*      X - number of bytes written to syslog       */
/****************************************************/
int sendSyslogMsg(char * procID, char * syslogMsg) 
{
    int bytes;
    UDP Udp;
    IPAddress remoteIP(10, 0, 0, 202);                      // IP address of syslog server
    char syslogBuffer[480];
    RFCSupport rfc = RFC5424;

    if (rfc == RFC3164)
        snprintf(syslogBuffer, sizeof(syslogBuffer), "<%d>%s %s %s:%s", PRIORITY, (const char *) Time.format("%b %d %H:%M:%S"), HOSTNAME, procID, syslogMsg);
    else if (rfc == RFC5424)
    {
        snprintf(syslogBuffer, sizeof(syslogBuffer), "<%d>%d %s.0-%s %s %s %s %s %s", 
            PRIORITY, VERSION, (const char *) Time.format("%Y-%m-%dT%H:%M:%S"), TZOFFSET, HOSTNAME, APPNAME, procID, STRUCTDATA, syslogMsg);
    }
    else return 0;
    Udp.begin(514);
    bytes = Udp.sendPacket(syslogBuffer, strlen(syslogBuffer), remoteIP, 514);
    Udp.stop();
#ifdef DEBUG
    snprintf(syslogBuffer, sizeof(syslogBuffer), "sendSyslogMsg(): Writing syslog entry = [%s]", syslogMsg);
    Serial.println(syslogBuffer);
#endif
    return bytes;
}

#endif

Also added an option to allow the user to provide a FQDN instead of an IP address for the syslog server. If you provide a hostname, the name is resolved into an IP address which is used for the syslog server. If you supply an IP address directly, it is used for the syslog server.

1 Like