Skip to content

Commit 58f9220

Browse files
committed
AN039 remote logging
1 parent 1bca59d commit 58f9220

File tree

6 files changed

+280
-0
lines changed

6 files changed

+280
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
#include "RemoteLogRK.h"
2+
#include "DeviceNameHelperRK.h"
3+
4+
SYSTEM_THREAD(ENABLED);
5+
6+
// Temporary log storage in retained memory
7+
retained uint8_t remoteLogBuf[2560];
8+
RemoteLog remoteLog(remoteLogBuf, sizeof(remoteLogBuf));
9+
10+
// Make sure you update to the host and port of your Papertrail logging instance!
11+
// Actually could be any UDP syslog server, not just Solarwinds Papertrail.
12+
const char *LOG_HOST = "logsXXX.papertrailapp.com";
13+
const uint16_t LOG_PORT = 39999;
14+
15+
// Where to store the device name in EEPROM
16+
const int EEPROM_OFFSET = 0;
17+
18+
SerialLogHandler serialLog;
19+
20+
void setup() {
21+
// For testing, you can wait for the serial port to be connected so you see
22+
// more log messages on USB serial
23+
// waitFor(Serial.isConnected, 10000);
24+
25+
// This example uses DeviceNameHelperEEPROM but any subclass can be used
26+
DeviceNameHelperEEPROM::instance().setup(EEPROM_OFFSET);
27+
28+
// Create a new remote log syslog over UDP logging server for Papertrail.
29+
RemoteLogSyslogUDP *logServer = new RemoteLogSyslogUDP(LOG_HOST, LOG_PORT);
30+
31+
// Since neither RemoteLogRK nor DeviceNameHelperRK libraries know about each
32+
// other, this bit of boilerplate code is needed to hook the two together.
33+
// You can use any DeviceNameHelper subclass, such as DeviceNameHelperFile
34+
// or DeviceNameHelperRetained here instead.
35+
logServer->withDeviceNameCallback([](String &deviceName) {
36+
if (DeviceNameHelperEEPROM::instance().hasName()) {
37+
deviceName = DeviceNameHelperEEPROM::instance().getName();
38+
return true;
39+
}
40+
else {
41+
return false;
42+
}
43+
});
44+
45+
// Finish setting up remoteLog
46+
remoteLog.withServer(logServer);
47+
remoteLog.setup();
48+
}
49+
50+
void loop() {
51+
DeviceNameHelperRetained::instance().loop();
52+
remoteLog.loop();
53+
54+
// This just generates some logs for testing purposes
55+
{
56+
static unsigned long lastLog = 0;
57+
static int counter = 0;
58+
59+
if (millis() - lastLog >= 10000) {
60+
lastLog = millis();
61+
Log.info("counter=%d memory=%lu", ++counter, System.freeMemory());
62+
}
63+
}
64+
}
583 KB
Loading
170 KB
Loading
168 KB
Loading
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
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+
![](/assets/images/app-notes/AN039/setup.png)
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+
![](/assets/images/app-notes/AN039/log1.png)
208+
209+
You might also want to use it to log things specific to your code. For example:
210+
211+
![](/assets/images/app-notes/AN039/log2.png)

src/content/datasheets/menu.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,11 @@
350350
"dir": "an038-libraries",
351351
"title": "AN038 Libraries",
352352
"href": "/datasheets/app-notes/an038-libraries/"
353+
},
354+
{
355+
"dir": "an039-remote-logging",
356+
"title": "AN039 Remote Logging",
357+
"href": "/datasheets/app-notes/an039-remote-logging/"
353358
}
354359
],
355360
{

0 commit comments

Comments
 (0)