Skip to content

Commit 31a8be2

Browse files
feat: Add macOS Unified Logging receiver implementation (#44434)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description Second PR for the `macosunifiedloggingreceiver`, adds the actual receiver implementation and unit tests. <!-- Issue number (e.g. #1234) or full URL to issue, if applicable. --> #### Link to tracking issue #44089 <!--Describe what testing was performed and which tests were added.--> #### Testing Added unit tests from downstream - confirmed all passing. Temporarily added the receiver to `builder-config.yaml`, confirmed the collector built and the receiver ran successfully. <!--Please delete paragraphs that you did not use before submitting.-->
1 parent 54a7fb3 commit 31a8be2

File tree

8 files changed

+1135
-100
lines changed

8 files changed

+1135
-100
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: new_component
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. receiver/filelog)
7+
component: receiver/macosunifiedlogging
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Implementation of the macOS Unified Logging Receiver.
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [44089]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext:
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: [user]

receiver/macosunifiedloggingreceiver/config_common.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ type Config struct {
1818
// resolvedArchivePaths stores the expanded archive paths after glob resolution
1919
// This is populated during Validate() and not exposed to users
2020
// Only used on darwin platform (see config.go)
21-
//nolint:unused
21+
//
22+
//nolint:unused // only used on darwin platform
2223
resolvedArchivePaths []string
2324

2425
// Predicate is a filter predicate to pass to the log command

receiver/macosunifiedloggingreceiver/config_test.go

Lines changed: 138 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
package macosunifiedloggingreceiver
77

88
import (
9-
"errors"
109
"os"
1110
"path/filepath"
1211
"testing"
@@ -19,108 +18,201 @@ import (
1918
func TestConfigValidate(t *testing.T) {
2019
testCases := []struct {
2120
desc string
22-
cfg *Config
23-
expectedErr error
21+
makeCfg func(t *testing.T) *Config
22+
expectedErr string
2423
}{
2524
{
2625
desc: "valid config - live mode",
27-
cfg: &Config{
28-
MaxPollInterval: 50 * time.Second,
29-
MaxLogAge: 12 * time.Hour,
26+
makeCfg: func(t *testing.T) *Config {
27+
return &Config{
28+
MaxPollInterval: 50 * time.Second,
29+
MaxLogAge: 12 * time.Hour,
30+
}
3031
},
31-
expectedErr: nil,
3232
},
3333
{
3434
desc: "invalid archive path - does not exist",
35-
cfg: &Config{
36-
ArchivePath: "/tmp/test/invalid",
35+
makeCfg: func(t *testing.T) *Config {
36+
return &Config{
37+
ArchivePath: filepath.Join(t.TempDir(), "missing", "logs.logarchive"),
38+
}
3739
},
38-
expectedErr: errors.New("no such file or directory"),
40+
expectedErr: "no such file or directory",
3941
},
4042
{
4143
desc: "invalid archive path - not a directory",
42-
cfg: &Config{
43-
ArchivePath: "./README.md",
44+
makeCfg: func(t *testing.T) *Config {
45+
return &Config{
46+
ArchivePath: "./README.md",
47+
}
48+
},
49+
expectedErr: "must be a directory",
50+
},
51+
{
52+
desc: "archive glob requires at least one match",
53+
makeCfg: func(t *testing.T) *Config {
54+
return &Config{
55+
ArchivePath: filepath.Join(t.TempDir(), "*.logarchive"),
56+
}
57+
},
58+
expectedErr: "no archive paths matched the provided pattern",
59+
},
60+
{
61+
desc: "end time requires archive path",
62+
makeCfg: func(t *testing.T) *Config {
63+
return &Config{
64+
EndTime: "2024-01-02 00:00:00",
65+
}
4466
},
45-
expectedErr: errors.New("must be a directory"),
67+
expectedErr: "end_time can only be used with archive_path",
4668
},
4769
{
4870
desc: "valid predicate with AND",
49-
cfg: &Config{
50-
Predicate: "subsystem == 'com.apple.example' AND messageType == 'Error'",
71+
makeCfg: func(t *testing.T) *Config {
72+
return &Config{
73+
Predicate: "subsystem == 'com.apple.example' AND messageType == 'Error'",
74+
}
5175
},
52-
expectedErr: nil,
5376
},
5477
{
5578
desc: "valid predicate with && (normalized to AND)",
56-
cfg: &Config{
57-
Predicate: "subsystem == 'com.apple.example' && messageType == 'Error'",
79+
makeCfg: func(t *testing.T) *Config {
80+
return &Config{
81+
Predicate: "subsystem == 'com.apple.example' && messageType == 'Error'",
82+
}
5883
},
59-
expectedErr: nil,
6084
},
6185
{
6286
desc: "valid predicate with || (normalized to OR)",
63-
cfg: &Config{
64-
Predicate: "subsystem == 'com.apple.example' || messageType == 'Error'",
87+
makeCfg: func(t *testing.T) *Config {
88+
return &Config{
89+
Predicate: "subsystem == 'com.apple.example' || messageType == 'Error'",
90+
}
6591
},
66-
expectedErr: nil,
6792
},
6893
{
6994
desc: "valid predicate with comparison operators",
70-
cfg: &Config{
71-
Predicate: "processID > 100 && processID < 1000",
95+
makeCfg: func(t *testing.T) *Config {
96+
return &Config{
97+
Predicate: "processID > 100 && processID < 1000",
98+
}
7299
},
73-
expectedErr: nil,
74100
},
75101
{
76102
desc: "valid predicate with > comparison and spaces",
77-
cfg: &Config{
78-
Predicate: "processID >100",
103+
makeCfg: func(t *testing.T) *Config {
104+
return &Config{
105+
Predicate: "processID >100",
106+
}
79107
},
80-
expectedErr: nil,
81108
},
82109
{
83110
desc: "invalid predicate - semicolon",
84-
cfg: &Config{
85-
Predicate: "subsystem == 'test'; curl http://evil.com",
111+
makeCfg: func(t *testing.T) *Config {
112+
return &Config{
113+
Predicate: "subsystem == 'test'; curl http://evil.com",
114+
}
86115
},
87-
expectedErr: errors.New("predicate contains invalid character"),
116+
expectedErr: "predicate contains invalid character",
88117
},
89118
{
90119
desc: "invalid predicate - pipe",
91-
cfg: &Config{
92-
Predicate: "subsystem == 'test' | sh",
120+
makeCfg: func(t *testing.T) *Config {
121+
return &Config{
122+
Predicate: "subsystem == 'test' | sh",
123+
}
93124
},
94-
expectedErr: errors.New("predicate contains invalid character"),
125+
expectedErr: "predicate contains invalid character",
95126
},
96127
{
97128
desc: "invalid predicate - dollar sign",
98-
cfg: &Config{
99-
Predicate: "subsystem == '$HOME'",
129+
makeCfg: func(t *testing.T) *Config {
130+
return &Config{
131+
Predicate: "subsystem == '$HOME'",
132+
}
100133
},
101-
expectedErr: errors.New("predicate contains invalid character"),
134+
expectedErr: "predicate contains invalid character",
102135
},
103136
{
104137
desc: "invalid predicate - backtick",
105-
cfg: &Config{
106-
Predicate: "subsystem == '`whoami`'",
138+
makeCfg: func(t *testing.T) *Config {
139+
return &Config{
140+
Predicate: "subsystem == '`whoami`'",
141+
}
107142
},
108-
expectedErr: errors.New("predicate contains invalid character"),
143+
expectedErr: "predicate contains invalid character",
109144
},
110145
{
111146
desc: "invalid predicate - append redirect",
112-
cfg: &Config{
113-
Predicate: "subsystem == 'test' >> /tmp/output",
147+
makeCfg: func(t *testing.T) *Config {
148+
return &Config{
149+
Predicate: "subsystem == 'test' >> /tmp/output",
150+
}
151+
},
152+
expectedErr: "predicate contains invalid character",
153+
},
154+
{
155+
desc: "predicate must contain valid field name",
156+
makeCfg: func(t *testing.T) *Config {
157+
return &Config{
158+
Predicate: "unknownField == 'value'",
159+
}
160+
},
161+
expectedErr: "predicate must contain at least one valid field name",
162+
},
163+
{
164+
desc: "predicate must contain operator",
165+
makeCfg: func(t *testing.T) *Config {
166+
return &Config{
167+
Predicate: "subsystem 'com.apple'",
168+
}
169+
},
170+
expectedErr: "predicate must contain at least one valid operator",
171+
},
172+
{
173+
desc: "predicate must contain valid event type when type is referenced",
174+
makeCfg: func(t *testing.T) *Config {
175+
return &Config{
176+
Predicate: "type == 'invalidEvent'",
177+
}
178+
},
179+
expectedErr: "predicate must contain at least one valid event type",
180+
},
181+
{
182+
desc: "predicate must contain valid log type when logType is referenced",
183+
makeCfg: func(t *testing.T) *Config {
184+
return &Config{
185+
Predicate: "logType == 'invalid'",
186+
}
187+
},
188+
expectedErr: "predicate must contain at least one valid log type",
189+
},
190+
{
191+
desc: "predicate must contain valid signpost scope when signpostScope is referenced",
192+
makeCfg: func(t *testing.T) *Config {
193+
return &Config{
194+
Predicate: "signpostScope == 'invalid'",
195+
}
196+
},
197+
expectedErr: "predicate must contain at least one valid signpost scope",
198+
},
199+
{
200+
desc: "predicate must contain valid signpost type when signpostType is referenced",
201+
makeCfg: func(t *testing.T) *Config {
202+
return &Config{
203+
Predicate: "signpostType == 'invalid'",
204+
}
114205
},
115-
expectedErr: errors.New("predicate contains invalid character"),
206+
expectedErr: "predicate must contain at least one valid signpost type",
116207
},
117208
}
118209

119210
for _, tc := range testCases {
120211
t.Run(tc.desc, func(t *testing.T) {
121-
err := tc.cfg.Validate()
122-
if tc.expectedErr != nil {
123-
require.ErrorContains(t, err, tc.expectedErr.Error())
212+
cfg := tc.makeCfg(t)
213+
err := cfg.Validate()
214+
if tc.expectedErr != "" {
215+
require.ErrorContains(t, err, tc.expectedErr)
124216
} else {
125217
require.NoError(t, err)
126218
}

receiver/macosunifiedloggingreceiver/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ require (
1818
)
1919

2020
require (
21+
github.com/cenkalti/backoff/v4 v4.3.0
2122
github.com/davecgh/go-spew v1.1.1 // indirect
2223
github.com/go-logr/logr v1.4.3 // indirect
2324
github.com/go-logr/stdr v1.2.2 // indirect

receiver/macosunifiedloggingreceiver/go.sum

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

receiver/macosunifiedloggingreceiver/receiver.go

Lines changed: 0 additions & 53 deletions
This file was deleted.

0 commit comments

Comments
 (0)