chore(otel/journaldexporter): add trusted journald fields (#20038)
journaldexporter: add truster journald fields
Ilya Mashchenko committed
Apr 2, 2025 at 23:48 UTC
31e8e9435a9271f131bf7a22925b8010e26ae427
4 files changed
+171
-39
src/go/otel-collector/exporter/journaldexporter/convert.go
+21
-17
@@ -3,6 +3,7 @@ package journaldexporter
3
import (
4
"bytes"
5
"encoding/binary"
6
+ "fmt"
7
"strconv"
8
"strings"
9
"time"
@@ -11,7 +12,8 @@ import (
12
"go.opentelemetry.io/collector/pdata/plog"
13
)
14
14
-func logsToJournaldMessages(ld plog.Logs, buf *bytes.Buffer) {
15
+func (e *journaldExporter) logsToJournaldMessages(ld plog.Logs, buf *bytes.Buffer) {
16
+ receivedAt := fmt.Sprintf("%d", time.Now().UnixNano()/1000)
17
rls := ld.ResourceLogs()
18
for i := 0; i < rls.Len(); i++ {
19
rl := rls.At(i)
@@ -26,8 +28,18 @@ func logsToJournaldMessages(ld plog.Logs, buf *bytes.Buffer) {
28
for k := 0; k < logRecords.Len(); k++ {
29
lr := logRecords.At(k)
30
31
+ writeField(buf, "__REALTIME_TIMESTAMP", receivedAt)
32
+ writeField(buf, "SYSLOG_IDENTIFIER", e.fields.syslogID)
33
+ writeField(buf, "_PID", e.fields.pid)
34
+ writeField(buf, "_UID", e.fields.uid)
35
+ writeField(buf, "_BOOT_ID", e.fields.bootID)
36
+ writeField(buf, "_MACHINE_ID", e.fields.machineID)
37
+ writeField(buf, "_HOSTNAME", e.fields.hostname)
38
+ writeField(buf, "PRIORITY", strconv.Itoa(mapSeverityToJournaldPriority(lr.SeverityNumber())))
39
+ writeField(buf, "MESSAGE", bodyToString(lr.Body()))
40
+
41
resource.Attributes().Range(func(k string, v pcommon.Value) bool {
30
- writeField(buf, "RESOURCE_"+k, v.AsString())
42
+ writeField(buf, "OTEL_RESOURCE_ATTR_"+k, v.AsString())
43
return true
44
})
45
@@ -40,7 +52,7 @@ func logsToJournaldMessages(ld plog.Logs, buf *bytes.Buffer) {
52
53
if lr.Timestamp() != 0 {
54
ts := time.Unix(0, int64(lr.Timestamp()))
43
- writeField(buf, "REALTIME_TIMESTAMP", strconv.FormatInt(ts.UnixMicro(), 10))
55
+ writeField(buf, "OTEL_TIMESTAMP", strconv.FormatInt(ts.UnixMicro(), 10))
56
}
57
58
if lr.ObservedTimestamp() != 0 && lr.ObservedTimestamp() != lr.Timestamp() {
@@ -48,34 +60,26 @@ func logsToJournaldMessages(ld plog.Logs, buf *bytes.Buffer) {
60
writeField(buf, "OTEL_OBSERVED_TIMESTAMP", strconv.FormatInt(ts.UnixMicro(), 10))
61
}
62
51
- priority := mapSeverityToJournaldPriority(lr.SeverityNumber())
52
- writeField(buf, "PRIORITY", strconv.Itoa(priority))
53
-
63
if lr.SeverityText() != "" {
55
- writeField(buf, "LEVEL", lr.SeverityText())
64
+ writeField(buf, "OTEL_SEVERITY_LEVEL", lr.SeverityText())
65
}
66
67
if !lr.TraceID().IsEmpty() {
59
- writeField(buf, "TRACE_ID", lr.TraceID().String())
68
+ writeField(buf, "OTEL_TRACE_ID", lr.TraceID().String())
69
if !lr.SpanID().IsEmpty() {
61
- writeField(buf, "SPAN_ID", lr.SpanID().String())
70
+ writeField(buf, "OTEL_SPAN_ID", lr.SpanID().String())
71
}
72
if lr.Flags() != 0 {
64
- writeField(buf, "TRACE_FLAGS", strconv.FormatUint(uint64(lr.Flags()), 16))
73
+ writeField(buf, "OTEL_TRACE_FLAGS", strconv.FormatUint(uint64(lr.Flags()), 16))
74
}
75
}
76
77
if lr.EventName() != "" {
69
- writeField(buf, "EVENT_NAME", lr.EventName())
70
- }
71
-
72
- bodyStr := bodyToString(lr.Body())
73
- if bodyStr != "" {
74
- writeField(buf, "MESSAGE", bodyStr)
78
+ writeField(buf, "OTEL_EVENT_NAME", lr.EventName())
79
}
80
81
lr.Attributes().Range(func(k string, v pcommon.Value) bool {
78
- writeField(buf, "ATTR_"+k, v.AsString())
82
+ writeField(buf, "OTEL_ATTR_"+k, v.AsString())
83
return true
84
})
85
src/go/otel-collector/exporter/journaldexporter/convert_test.go
+49
-15
@@ -3,6 +3,7 @@ package journaldexporter
3
import (
4
"bytes"
5
"encoding/binary"
6
+ "strings"
7
"testing"
8
"time"
9
@@ -41,16 +42,23 @@ func TestLogsToJournaldMessages(t *testing.T) {
42
43
return logs
44
},
44
- expected: `RESOURCE_SERVICE_NAME=test-service
45
-RESOURCE_SERVICE_INSTANCE_ID=instance-1
46
-OTEL_SCOPE_NAME=test-scope
47
-OTEL_SCOPE_VERSION=v1.0.0
48
-REALTIME_TIMESTAMP=1617030613000000
45
+ expected: `__REALTIME_TIMESTAMP=
46
+SYSLOG_IDENTIFIER=test-syslog-id
47
+_PID=test-pid
48
+_UID=test-uid
49
+_BOOT_ID=test-boot-id
50
+_MACHINE_ID=test-machine-id
51
+_HOSTNAME=test-hostname
52
PRIORITY=6
50
-LEVEL=INFO
53
MESSAGE=This is a test message
52
-ATTR_HTTP_METHOD=GET
53
-ATTR_HTTP_STATUS_CODE=200
54
+OTEL_RESOURCE_ATTR_SERVICE_NAME=test-service
55
+OTEL_RESOURCE_ATTR_SERVICE_INSTANCE_ID=instance-1
56
+OTEL_SCOPE_NAME=test-scope
57
+OTEL_SCOPE_VERSION=v1.0.0
58
+OTEL_TIMESTAMP=1617030613000000
59
+OTEL_SEVERITY_LEVEL=INFO
60
+OTEL_ATTR_HTTP_METHOD=GET
61
+OTEL_ATTR_HTTP_STATUS_CODE=200
62
63
`,
64
},
@@ -73,12 +81,19 @@ ATTR_HTTP_STATUS_CODE=200
81
82
return logs
83
},
76
- expected: `PRIORITY=3
77
-LEVEL=ERROR
78
-TRACE_ID=0102030405060708090a0b0c0d0e0f10
79
-SPAN_ID=0102030405060708
80
-TRACE_FLAGS=1
84
+ expected: `__REALTIME_TIMESTAMP=
85
+SYSLOG_IDENTIFIER=test-syslog-id
86
+_PID=test-pid
87
+_UID=test-uid
88
+_BOOT_ID=test-boot-id
89
+_MACHINE_ID=test-machine-id
90
+_HOSTNAME=test-hostname
91
+PRIORITY=3
92
MESSAGE=Connection failed
93
+OTEL_SEVERITY_LEVEL=ERROR
94
+OTEL_TRACE_ID=0102030405060708090a0b0c0d0e0f10
95
+OTEL_SPAN_ID=0102030405060708
96
+OTEL_TRACE_FLAGS=1
97
98
`,
99
},
@@ -96,6 +111,13 @@ MESSAGE=Connection failed
111
},
112
expected: func() string {
113
var buf bytes.Buffer
114
+ buf.WriteString("__REALTIME_TIMESTAMP=\n")
115
+ buf.WriteString("SYSLOG_IDENTIFIER=test-syslog-id\n")
116
+ buf.WriteString("_PID=test-pid\n")
117
+ buf.WriteString("_UID=test-uid\n")
118
+ buf.WriteString("_BOOT_ID=test-boot-id\n")
119
+ buf.WriteString("_MACHINE_ID=test-machine-id\n")
120
+ buf.WriteString("_HOSTNAME=test-hostname\n")
121
buf.WriteString("PRIORITY=3\n")
122
buf.WriteString("MESSAGE\n")
123
multilineMsg := "Error occurred:\nStack trace:\n at function1()\n at function2()\n at main()"
@@ -110,9 +132,21 @@ MESSAGE=Connection failed
132
for name, tc := range tests {
133
t.Run(name, func(t *testing.T) {
134
var buf bytes.Buffer
113
- logsToJournaldMessages(tc.logsFn(), &buf)
135
+ e := journaldExporter{fields: commonFields{
136
+ syslogID: "test-syslog-id",
137
+ pid: "test-pid",
138
+ uid: "test-uid",
139
+ hostname: "test-hostname",
140
+ bootID: "test-boot-id",
141
+ machineID: "test-machine-id",
142
+ }}
143
+
144
+ e.logsToJournaldMessages(tc.logsFn(), &buf)
145
+
146
+ ts, _, _ := strings.Cut(buf.String(), "\n")
147
+ _, expected, _ := strings.Cut(tc.expected, "\n")
148
115
- assert.Equal(t, tc.expected, buf.String())
149
+ assert.Equal(t, ts+"\n"+expected, buf.String())
150
})
151
}
152
}
src/go/otel-collector/exporter/journaldexporter/exporter.go
+28
-7
@@ -3,16 +3,30 @@ package journaldexporter
3
import (
4
"context"
5
"fmt"
6
+ "os"
7
+ "os/user"
8
+ "strconv"
9
10
"go.opentelemetry.io/collector/component"
11
"go.opentelemetry.io/collector/pdata/plog"
12
"go.uber.org/zap"
13
)
14
12
-type journaldExporter struct {
13
- log *zap.Logger
14
- conf *Config
15
-}
15
+type (
16
+ journaldExporter struct {
17
+ log *zap.Logger
18
+ conf *Config
19
+ fields commonFields
20
+ }
21
+ commonFields struct {
22
+ syslogID string
23
+ pid string
24
+ uid string
25
+ bootID string
26
+ machineID string
27
+ hostname string
28
+ }
29
+)
30
31
func newJournaldExporter(cfg component.Config, logger *zap.Logger) *journaldExporter {
32
return &journaldExporter{
@@ -26,9 +40,16 @@ func (e *journaldExporter) consumeLogs(_ context.Context, ld plog.Logs) error {
40
return nil
41
}
42
29
-func (e *journaldExporter) Start(_ context.Context, host component.Host) error {
30
- host.GetExtensions()
31
- fmt.Println("Starting MyExporter")
43
+func (e *journaldExporter) Start(_ context.Context, _ component.Host) error {
44
+ e.fields.syslogID = "nd-otel-collector"
45
+ e.fields.pid = strconv.Itoa(os.Getpid())
46
+ e.fields.hostname, _ = os.Hostname()
47
+ e.fields.bootID = getBootID()
48
+ e.fields.machineID = getMachineID()
49
+ if cu, err := user.Current(); err == nil {
50
+ e.fields.uid = cu.Uid
51
+ }
52
+
53
return nil
54
}
55
src/go/otel-collector/exporter/journaldexporter/sys.go
new
+73
@@ -0,0 +1,73 @@
1
+package journaldexporter
2
+
3
+import (
4
+ "os"
5
+ "os/exec"
6
+ "regexp"
7
+ "runtime"
8
+ "strings"
9
+
10
+ "github.com/google/uuid"
11
+)
12
+
13
+func getBootID() string {
14
+ switch runtime.GOOS {
15
+ case "linux":
16
+ if bs, err := os.ReadFile("/proc/sys/kernel/random/boot_id"); err == nil {
17
+ return strings.TrimSpace(string(bs))
18
+ }
19
+ case "darwin", "dragonfly", "freebsd", "netbsd", "openbsd":
20
+ cmd := exec.Command("sysctl", "kern.boottime")
21
+ if bs, err := cmd.Output(); err == nil && len(bs) > 0 {
22
+ return uuid.NewSHA1(uuid.NameSpaceDNS, bs).String()
23
+ }
24
+ case "windows":
25
+ cmd := exec.Command("powershell", "-Command",
26
+ "(Get-CimInstance -ClassName win32_operatingsystem).LastBootUpTime.ToString('o')")
27
+ if bs, err := cmd.Output(); err == nil && len(bs) > 0 {
28
+ return uuid.NewSHA1(uuid.NameSpaceDNS, bs).String()
29
+ }
30
+
31
+ cmd = exec.Command("wmic", "os", "get", "LastBootUpTime")
32
+ if bs, err := cmd.Output(); err == nil && len(bs) > 0 {
33
+ return uuid.NewSHA1(uuid.NameSpaceDNS, bs).String()
34
+ }
35
+ }
36
+
37
+ return uuid.NewString()
38
+}
39
+
40
+func getMachineID() string {
41
+ switch runtime.GOOS {
42
+ case "linux":
43
+ if bs, err := os.ReadFile("/etc/machine-id"); err == nil {
44
+ return strings.TrimSpace(string(bs))
45
+ }
46
+ case "dragonfly", "freebsd", "netbsd", "openbsd":
47
+ if bs, err := os.ReadFile("/etc/hostid"); err == nil {
48
+ return strings.TrimSpace(string(bs))
49
+ }
50
+ case "windows":
51
+ cmd := exec.Command("powershell", "-Command",
52
+ "Get-ItemProperty -Path 'HKLM:\\SOFTWARE\\Microsoft\\Cryptography' -Name 'MachineGuid'")
53
+ if bs, err := cmd.Output(); err == nil && len(bs) > 0 {
54
+ re := regexp.MustCompile(`MachineGuid\s+:\s+([0-9a-fA-F-]+)`)
55
+ matches := re.FindSubmatch(bs)
56
+ if len(matches) >= 2 {
57
+ return string(matches[1])
58
+ }
59
+ }
60
+
61
+ cmd = exec.Command("wmic", "csproduct", "get", "UUID")
62
+ if bs, err := cmd.Output(); err == nil && len(bs) > 0 {
63
+ lines := strings.Split(string(bs), "\n")
64
+ if len(lines) > 1 {
65
+ return strings.TrimSpace(lines[1])
66
+ }
67
+ }
68
+ }
69
+
70
+ hostname, _ := os.Hostname()
71
+
72
+ return uuid.NewSHA1(uuid.NameSpaceDNS, []byte(hostname)).String()
73
+}