@cryptotaxi247 / netdata-1 / commits / 31e8e9435

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 +}