|
| 1 | +--- |
| 2 | +title: AN039 Remote Logging |
| 3 | +layout: commonTwo.hbs |
| 4 | +columns: two |
| 5 | +--- |
| 6 | + |
| 7 | +# AN039 Remote Logging |
| 8 | + |
| 9 | +We recommend using the [Logging system](/cards/firmware/logging/logging/) built into Device OS. For example: |
| 10 | + |
| 11 | +```cpp |
| 12 | +// Use primary serial over USB interface for logging output |
| 13 | +SerialLogHandler logHandler; |
| 14 | + |
| 15 | +void setup() { |
| 16 | + // Log some messages with different logging levels |
| 17 | + Log.info("This is info message"); |
| 18 | + Log.warn("This is warning message"); |
| 19 | + Log.error("This is error message, error=%d", errCode); |
| 20 | + |
| 21 | + // Format text message |
| 22 | + Log.info("System version: %s", (const char*)System.version()); |
| 23 | +} |
| 24 | + |
| 25 | +void loop() { |
| 26 | +} |
| 27 | +``` |
| 28 | + |
| 29 | +There are a number of advantages over using `Serial.print` calls: |
| 30 | + |
| 31 | +- The `Log` class is thread-safe. |
| 32 | +- You can easily redirect logs to `Serial1` (hardware UART) instead of `Serial`. |
| 33 | +- You can control the log level of individual modules separately. |
| 34 | +- You can add other log handlers to write to a [remote log](https://github.com/rickkas7/RemoteLogRK), [SD card](https://github.com/rickkas7/SdCardLogHandlerRK), and more. |
| 35 | + |
| 36 | +## Remote logging |
| 37 | + |
| 38 | +The [remote logging](https://github.com/rickkas7/RemoteLogRK) library supports several modes of operation, however this note will concentrate on using it with syslog over UDP. There are pros and cons of this: |
| 39 | + |
| 40 | +- This method is best with Wi-Fi devices. |
| 41 | +- It works with cellular, but beware of cellular data limits if you generate a lot of logs. |
| 42 | +- Data is sent unencrypted. |
| 43 | + |
| 44 | +It is possible to set up your own syslog server on a computer on your local network, but this note will concentrate on using it with a commercial service, [SolarWinds Papertrail](https://www.papertrail.com/). Papertrail has a nice web-based user interface, fast searching, and live tail for displaying logs as they are generated. |
| 45 | + |
| 46 | +- If you are a hobbyist with a very small number of devices, you may be able to use the free tier (50 MB/month) |
| 47 | +- If you are a hobbyist with a medium number of devices but with some money to spend, the 1 GB, 2 GB, or 4 GB plans are reasonable. |
| 48 | +- If you are a product developer and want to use this for beta test devices, it will likely be affordable. |
| 49 | + |
| 50 | +Logging an entire large device fleet would likely be cost-prohibitive, and for cellular, would likely run into cellular data limits or incur additional block charges. |
| 51 | + |
| 52 | +However, during development and testing in particular, being able to see your logs is invaluable for troubleshooting. |
| 53 | + |
| 54 | +## Papertrail setup |
| 55 | + |
| 56 | +In addition to creating your account, you will need a **Log destination** from the Settings - Log Destinations menu. Click **Create Log Destination**. |
| 57 | + |
| 58 | + |
| 59 | + |
| 60 | +In particular, make sure you select: |
| 61 | + |
| 62 | +- Accept connections via: **Port** |
| 63 | +- Check the **USB: Plain text** box |
| 64 | + |
| 65 | +When you create your log destination, you'll get something like this back: |
| 66 | + |
| 67 | +logs999.papertrailapp.com:44567 |
| 68 | + |
| 69 | +The part before the : is the host (logs999.papertrailapp.com), and the port is 44567 in this made-up example. You should keep this relatively secret because anyone with this host and port can log to your log destination if they know this and you have the **Yes, recognize logs from new systems** box checked. If your set of devices is relatively constant, you may want to uncheck this box. |
| 70 | + |
| 71 | +## Library setup |
| 72 | + |
| 73 | +### Device name or identifier |
| 74 | + |
| 75 | +One thing you'll need to decide is how you want to identify the systems in the logs: |
| 76 | + |
| 77 | +- Device name |
| 78 | +- Device ID |
| 79 | +- Custom method |
| 80 | + |
| 81 | +Using the device name is convenient, however: |
| 82 | + |
| 83 | +- It requires a request to the cloud to get the device name, which is two data operations |
| 84 | +- It cannot be done from unclaimed product devices |
| 85 | +- To reduce data operations, you can store the name in EEPROM. |
| 86 | + |
| 87 | +The easiest way to use the device name is by using the **DeviceNameHelperRK** library. You can find the [full documentation on Github](https://github.com/rickkas7/DeviceNameHelperRK). |
| 88 | + |
| 89 | +If you do not want to use the device name, the device ID is a good alternative. |
| 90 | + |
| 91 | +### Add library |
| 92 | + |
| 93 | +If you are using Particle Workbench, from the Command Palette (Ctrl-Shift-P or Command-Shift-P), select **Particle: Install Library** and install **RemoteLogRK** and **DeviceNameHelperRK** (if you are using the device name). |
| 94 | + |
| 95 | +If you are using the Web IDE, click the **Libraries** icon and search for **RemoteLogRK** and add it to your project. Repeat for **DeviceNameHelperRK**. |
| 96 | + |
| 97 | + |
| 98 | +### Example with device name |
| 99 | + |
| 100 | + |
| 101 | +{{> codebox content="/assets/files/app-notes/AN039/papertrail-example.cpp" format="cpp" height="500"}} |
| 102 | + |
| 103 | + |
| 104 | +## Code walk-through |
| 105 | + |
| 106 | +This example stores log entries in retained memory, so they are preserved if the device reboots. It also |
| 107 | +allows logs generated early in startup, before the Wi-Fi or cellular network is up, to be preserved. |
| 108 | + |
| 109 | +You can make the value 2560 smaller if you are using retained memory for other purposes, or a little |
| 110 | +larger, though the limit is 3068 bytes on both Gen 2 and Gen 3 devices. |
| 111 | + |
| 112 | +```cpp |
| 113 | +retained uint8_t remoteLogBuf[2560]; |
| 114 | +RemoteLog remoteLog(remoteLogBuf, sizeof(remoteLogBuf)); |
| 115 | +``` |
| 116 | +
|
| 117 | +Here's where you update the host and port to match what you got when you configured your log destination. |
| 118 | +
|
| 119 | +```cpp |
| 120 | +const char *LOG_HOST = "logsXXX.papertrailapp.com"; |
| 121 | +const uint16_t LOG_PORT = 39999; |
| 122 | +``` |
| 123 | + |
| 124 | +This example stores the device name in EEPROM. If you are already using the EEPROM, set this to be an offset |
| 125 | +after your data. |
| 126 | + |
| 127 | +```cpp |
| 128 | +const int EEPROM_OFFSET = 0; |
| 129 | +``` |
| 130 | + |
| 131 | +You can still enable the USB serial debug log, so logs will go to both: |
| 132 | + |
| 133 | +```cpp |
| 134 | +SerialLogHandler serialLog; |
| 135 | +``` |
| 136 | + |
| 137 | +This, in setup(), is part of the device name retrival. There's also a line in loop(). |
| 138 | + |
| 139 | +```cpp |
| 140 | +DeviceNameHelperEEPROM::instance().setup(EEPROM_OFFSET); |
| 141 | +``` |
| 142 | +
|
| 143 | +This sets up remote logging to syslog over UDP, the mode to use for Papertrail. |
| 144 | +
|
| 145 | +```cpp |
| 146 | +RemoteLogSyslogUDP *logServer = new RemoteLogSyslogUDP(LOG_HOST, LOG_PORT); |
| 147 | +``` |
| 148 | + |
| 149 | +Since the device name helper and remote logging libraries do not know about each other, this bit of boilerplate code links the two together: |
| 150 | + |
| 151 | +```cpp |
| 152 | +logServer->withDeviceNameCallback([](String &deviceName) { |
| 153 | + if (DeviceNameHelperEEPROM::instance().hasName()) { |
| 154 | + deviceName = DeviceNameHelperEEPROM::instance().getName(); |
| 155 | + return true; |
| 156 | + } |
| 157 | + else { |
| 158 | + return false; |
| 159 | + } |
| 160 | +}); |
| 161 | +``` |
| 162 | +
|
| 163 | +This completes the setup of remote logging: |
| 164 | +
|
| 165 | +```cpp |
| 166 | +remoteLog.withServer(logServer); |
| 167 | +remoteLog.setup(); |
| 168 | +``` |
| 169 | + |
| 170 | +These two lines are required in loop() if using the device name: |
| 171 | + |
| 172 | +```cpp |
| 173 | +DeviceNameHelperRetained::instance().loop(); |
| 174 | +remoteLog.loop(); |
| 175 | +``` |
| 176 | + |
| 177 | +This code just generates a log message every 10 seconds for testing. You almost certainly do not want to add this to your production code! |
| 178 | + |
| 179 | +```cpp |
| 180 | +static unsigned long lastLog = 0; |
| 181 | +static int counter = 0; |
| 182 | + |
| 183 | +if (millis() - lastLog >= 10000) { |
| 184 | + lastLog = millis(); |
| 185 | + Log.info("counter=%d memory=%lu", ++counter, System.freeMemory()); |
| 186 | +} |
| 187 | +``` |
| 188 | + |
| 189 | + |
| 190 | +### Example with Device ID |
| 191 | + |
| 192 | +If you only want to use the Device ID as the identifier in your logs, the device name callback is simply: |
| 193 | + |
| 194 | +```cpp |
| 195 | +logServer->withDeviceNameCallback([](String &deviceName) { |
| 196 | + deviceName = System.deviceID(); |
| 197 | + return true; |
| 198 | +}); |
| 199 | +``` |
| 200 | +
|
| 201 | +You do not need to include the **DeviceNameHelperRK** and do not need to use any EEPROM if using the Device ID as the logging identifier. |
| 202 | +
|
| 203 | +### Example output |
| 204 | +
|
| 205 | +In this example, the device does not generate many logs, but you can easily see when it needs to refresh its cloud session every 3 days: |
| 206 | +
|
| 207 | + |
| 208 | +
|
| 209 | +You might also want to use it to log things specific to your code. For example: |
| 210 | +
|
| 211 | + |
0 commit comments