Skip to content

Commit 8f64afe

Browse files
mvicknrclaude
andauthored
chore: Update logging and send errors to NR (#26)
* chore: Update logging * chore: Fix env var * chore: Add trace ids to instrumentation * test: Force a failure * Revert "test: Force a failure" This reverts commit c0b952f. * chore: Notice errors * test: Force error to test * fix: Fix so nrApp is shutdown properly * revert: Manually revert test of NR notice error * chore: Cleanup * chore: Fix gitignore --------- Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 488420a commit 8f64afe

File tree

21 files changed

+606
-201
lines changed

21 files changed

+606
-201
lines changed

.github/workflows/test_action.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ on:
77
push:
88
branches:
99
- '**'
10+
schedule:
11+
- cron: '0 1 * * *' # This runs the workflow daily at 1am UTC
1012

1113
jobs:
1214
setup-test-tag:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ agent-metadata-action
77

88
# Go coverage
99
coverage.out
10+
coverage.html
1011

1112
# IDE
1213
.idea/

cmd/agent-metadata-action/main.go

Lines changed: 94 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,20 @@ var ociHandleUploadsFunc = oci.HandleUploads
3636

3737
// initNewRelic initializes the New Relic application
3838
// Returns nil if APM_CONTROL_NR_LICENSE_KEY is not set (silent no-op mode)
39-
func initNewRelic() *newrelic.Application {
39+
func initNewRelic(ctx context.Context) *newrelic.Application {
4040
licenseKey := config.GetNRAgentLicenseKey()
4141
if licenseKey == "" {
42-
_, _ = fmt.Fprintln(os.Stderr, "::warn::Failed to init New Relic - missing license key")
42+
logging.Warn(ctx, "Failed to init New Relic - missing license key")
4343
return nil
4444
}
4545

4646
// Hardcode staging environment
4747
err := config.SetNRAgentHost()
4848
if err != nil {
49-
_, _ = fmt.Fprintf(os.Stderr, "::warn::Failed to init New Relic, missing host: %v\n", err)
49+
logging.Warnf(ctx, "Failed to init New Relic, missing host: %v", err)
5050
return nil
5151
}
52-
fmt.Println("::notice::Using New Relic staging environment")
52+
logging.Notice(ctx, "Using New Relic staging environment")
5353

5454
app, err := newrelic.NewApplication(
5555
newrelic.ConfigAppName("agent-metadata-action"),
@@ -64,34 +64,43 @@ func initNewRelic() *newrelic.Application {
6464
)
6565

6666
if err != nil {
67-
_, _ = fmt.Fprintf(os.Stderr, "::warn::Failed to init New Relic: %v\n", err)
67+
logging.Warnf(ctx, "Failed to init New Relic: %v", err)
6868
return nil
6969
}
7070

71-
fmt.Println("::notice::New Relic APM enabled - waiting for connection...")
71+
logging.Notice(ctx, "New Relic APM enabled - waiting for connection...")
7272

7373
// Wait for the app to connect (max 10 seconds)
7474
if err := app.WaitForConnection(10 * time.Second); err != nil {
75-
fmt.Printf("::warn::New Relic connection timeout: %v - will try to send data anyway\n", err)
75+
logging.Warnf(ctx, "New Relic connection timeout: %v - will try to send data anyway", err)
7676
} else {
77-
fmt.Println("::notice::New Relic connected successfully")
77+
logging.Notice(ctx, "New Relic connected successfully")
7878
}
7979

8080
return app
8181
}
8282

8383
func main() {
84-
nrApp := initNewRelic()
84+
// Create base context for early logging
85+
ctx := context.Background()
8586

86-
if err := run(nrApp); err != nil {
87-
_, _ = fmt.Fprintf(os.Stderr, "::error::%v\n", err)
88-
os.Exit(1)
89-
}
87+
nrApp := initNewRelic(ctx)
88+
89+
// Run the action
90+
err := run(nrApp)
9091

92+
// Ensure New Relic shuts down gracefully (even on error)
93+
// Must happen BEFORE os.Exit() since os.Exit bypasses defers
9194
if nrApp != nil {
92-
fmt.Println("::notice::Shutting down New Relic - waiting up to 15 seconds to send data...")
95+
logging.Notice(ctx, "Shutting down New Relic - waiting up to 15 seconds to send data...")
9396
nrApp.Shutdown(15 * time.Second)
94-
fmt.Println("::notice::New Relic shutdown complete")
97+
logging.Notice(ctx, "New Relic shutdown complete")
98+
}
99+
100+
// Exit with appropriate code
101+
if err != nil {
102+
logging.Errorf(ctx, "%v", err)
103+
os.Exit(1)
95104
}
96105
}
97106

@@ -134,16 +143,31 @@ func run(nrApp *newrelic.Application) error {
134143
func validateEnvironment(ctx context.Context) (workspace string, token string, err error) {
135144
workspace = config.GetWorkspace()
136145
if workspace == "" {
137-
return "", "", fmt.Errorf("GITHUB_WORKSPACE is required but not set")
146+
err := fmt.Errorf("GITHUB_WORKSPACE is required but not set")
147+
logging.NoticeErrorWithCategory(ctx, err, "environment.validation", map[string]interface{}{
148+
"error.operation": "validate_workspace",
149+
"error.field": "GITHUB_WORKSPACE",
150+
})
151+
return "", "", err
138152
}
139153

140154
if _, err := os.Stat(workspace); err != nil {
141-
return "", "", fmt.Errorf("workspace directory does not exist: %s", workspace)
155+
noticeErr := fmt.Errorf("workspace directory does not exist: %s", workspace)
156+
logging.NoticeErrorWithCategory(ctx, noticeErr, "environment.validation", map[string]interface{}{
157+
"error.operation": "validate_workspace",
158+
"workspace.path": workspace,
159+
})
160+
return "", "", noticeErr
142161
}
143162

144163
token = config.GetToken()
145164
if token == "" {
146-
return "", "", fmt.Errorf("NEWRELIC_TOKEN is required but not set")
165+
err := fmt.Errorf("NEWRELIC_TOKEN is required but not set")
166+
logging.NoticeErrorWithCategory(ctx, err, "environment.validation", map[string]interface{}{
167+
"error.operation": "validate_token",
168+
"error.field": "NEWRELIC_TOKEN",
169+
})
170+
return "", "", err
147171
}
148172

149173
logging.Notice(ctx, "Environment validated successfully")
@@ -152,7 +176,7 @@ func validateEnvironment(ctx context.Context) (workspace string, token string, e
152176

153177
// runAgentFlow handles the agent repository workflow
154178
func runAgentFlow(ctx context.Context, client metadataClient, workspace, agentType, agentVersion string) error {
155-
logging.Debug(ctx, "Running agent repository flow")
179+
logging.Debugf(ctx, "Running agent repository flow for %s version %s", agentType, agentVersion)
156180

157181
// Check for .fleetControl directory
158182
fleetControlPath := filepath.Join(workspace, config.GetRootFolderForAgentRepo())
@@ -161,15 +185,28 @@ func runAgentFlow(ctx context.Context, client metadataClient, workspace, agentTy
161185
}
162186

163187
// Load configuration definitions (required)
164-
configs, err := loader.ReadConfigurationDefinitions(workspace)
188+
configs, err := loader.ReadConfigurationDefinitions(ctx, workspace)
165189
if err != nil {
190+
logging.NoticeErrorWithCategory(ctx, err, "configuration.load", map[string]interface{}{
191+
"error.operation": "load_configuration_definitions",
192+
"agent.type": agentType,
193+
"agent.version": agentVersion,
194+
"workflow.type": "agent",
195+
})
166196
return fmt.Errorf("failed to read configuration definitions: %w", err)
167197
}
168198
logging.Noticef(ctx, "Loaded %d configuration definitions", len(configs))
169199

170200
// Load agent control definitions (optional)
171-
agentControl, err := loader.ReadAgentControlDefinitions(workspace)
201+
agentControl, err := loader.ReadAgentControlDefinitions(ctx, workspace)
172202
if err != nil {
203+
logging.NoticeErrorWithCategory(ctx, err, "configuration.load", map[string]interface{}{
204+
"error.operation": "load_agent_control_definitions",
205+
"agent.type": agentType,
206+
"agent.version": agentVersion,
207+
"workflow.type": "agent",
208+
"error.severity": "warning", // Graceful error
209+
})
173210
logging.Warnf(ctx, "Unable to load agent control definitions: %v - continuing without them", err)
174211
agentControl = nil
175212
} else {
@@ -183,21 +220,38 @@ func runAgentFlow(ctx context.Context, client metadataClient, workspace, agentTy
183220
AgentControlDefinitions: agentControl,
184221
}
185222

186-
printJSON("Agent Metadata", metadata)
223+
printJSON(ctx, "Agent Metadata", metadata)
187224

188225
// Step 1: Send to metadata service
189226
if err := client.SendMetadata(ctx, agentType, agentVersion, &metadata); err != nil {
227+
logging.NoticeErrorWithCategory(ctx, err, "metadata.send", map[string]interface{}{
228+
"error.operation": "send_metadata",
229+
"agent.type": agentType,
230+
"agent.version": agentVersion,
231+
"workflow.type": "agent",
232+
})
190233
return fmt.Errorf("failed to send metadata for %s: %w", agentType, err)
191234
}
192235

193236
ociConfig, err := oci.LoadConfig()
194237
if err != nil {
238+
logging.NoticeErrorWithCategory(ctx, err, "oci.configuration", map[string]interface{}{
239+
"error.operation": "load_oci_config",
240+
"agent.type": agentType,
241+
"agent.version": agentVersion,
242+
})
195243
return fmt.Errorf("error loading OCI config: %w", err)
196244
}
197245

198246
// Step 2: Upload binaries
199-
uploadResults, err := ociHandleUploadsFunc(&ociConfig, workspace, agentType, agentVersion)
247+
uploadResults, err := ociHandleUploadsFunc(ctx, &ociConfig, workspace, agentType, agentVersion)
200248
if err != nil {
249+
logging.NoticeErrorWithCategory(ctx, err, "oci.upload", map[string]interface{}{
250+
"error.operation": "upload_binaries",
251+
"oci.registry": ociConfig.Registry,
252+
"agent.type": agentType,
253+
"agent.version": agentVersion,
254+
})
201255
return fmt.Errorf("binary upload failed: %w", err)
202256
}
203257

@@ -225,7 +279,7 @@ func runAgentFlow(ctx context.Context, client metadataClient, workspace, agentTy
225279
return fmt.Errorf("NEWRELIC_TOKEN is required for artifact signing")
226280
}
227281

228-
if err := sign.SignArtifacts(successfulUploads, ociConfig.Registry, token, repoName, agentVersion); err != nil {
282+
if err := sign.SignArtifacts(ctx, successfulUploads, ociConfig.Registry, token, repoName, agentVersion); err != nil {
229283
return fmt.Errorf("artifact signing failed: %w", err)
230284
}
231285
} else {
@@ -242,8 +296,12 @@ func runDocsFlow(ctx context.Context, client metadataClient) error {
242296
logging.Debug(ctx, "Running documentation flow")
243297

244298
// Load metadata from changed MDX files
245-
metadataList, err := loader.LoadMetadataForDocs()
299+
metadataList, err := loader.LoadMetadataForDocs(ctx)
246300
if err != nil {
301+
logging.NoticeErrorWithCategory(ctx, err, "docs.load", map[string]interface{}{
302+
"error.operation": "load_metadata_from_docs",
303+
"workflow.type": "docs",
304+
})
247305
return fmt.Errorf("failed to load metadata from docs: %w", err)
248306
}
249307

@@ -258,7 +316,13 @@ func runDocsFlow(ctx context.Context, client metadataClient) error {
258316
successCount := 0
259317
for _, entry := range metadataList {
260318
if err := sendDocsMetadata(ctx, client, entry); err != nil {
261-
logging.Warnf(ctx, "Failed to send metadata for %s: %v", entry.AgentType, err)
319+
logging.NoticeErrorWithCategory(ctx, err, "docs.send", map[string]interface{}{
320+
"error.operation": "send_docs_metadata",
321+
"agent.type": entry.AgentType,
322+
"workflow.type": "docs",
323+
"error.severity": "warning", // Graceful error
324+
})
325+
logging.Errorf(ctx, "Failed to send metadata for %s: %v", entry.AgentType, err)
262326
continue
263327
}
264328
successCount++
@@ -276,7 +340,7 @@ func sendDocsMetadata(ctx context.Context, client metadataClient, entry loader.M
276340
Metadata: entry.AgentMetadataFromDocs,
277341
}
278342

279-
printJSON(fmt.Sprintf("Docs Metadata (%s %s)", entry.AgentType, version), entry.AgentMetadataFromDocs)
343+
printJSON(ctx, fmt.Sprintf("Docs Metadata (%s %s)", entry.AgentType, version), entry.AgentMetadataFromDocs)
280344

281345
if err := client.SendMetadata(ctx, entry.AgentType, version, &metadata); err != nil {
282346
return err
@@ -287,11 +351,11 @@ func sendDocsMetadata(ctx context.Context, client metadataClient, entry loader.M
287351
}
288352

289353
// printJSON marshals data to JSON and prints it with a debug annotation
290-
func printJSON(label string, data any) {
354+
func printJSON(ctx context.Context, label string, data any) {
291355
jsonData, err := json.MarshalIndent(data, "", " ")
292356
if err != nil {
293-
fmt.Printf("::debug::Failed to marshal %s: %v\n", label, err)
357+
logging.Debugf(ctx, "Failed to marshal %s: %v", label, err)
294358
return
295359
}
296-
fmt.Printf("::debug::%s: %s\n", label, string(jsonData))
360+
logging.Debugf(ctx, "%s: %s", label, string(jsonData))
297361
}

cmd/agent-metadata-action/main_test.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ Release notes content here.
158158

159159
// Mock GetChangedMDXFiles to return test MDX files
160160
originalFunc := github.GetChangedMDXFilesFunc
161-
github.GetChangedMDXFilesFunc = func() ([]string, error) {
161+
github.GetChangedMDXFilesFunc = func(ctx context.Context) ([]string, error) {
162162
return []string{testMDXFile}, nil
163163
}
164164
defer func() {
@@ -175,11 +175,11 @@ Release notes content here.
175175
main()
176176

177177
outputStr := getStdout()
178-
stderrStr := getStderr()
178+
_ = getStderr()
179179

180180
// Verify docs scenario was triggered
181181
assert.Contains(t, outputStr, "Running documentation flow")
182-
assert.Contains(t, stderrStr, "::notice::Loaded metadata for 1 out of 1 changed MDX files")
182+
assert.Contains(t, outputStr, "::notice::Loaded metadata for 1 out of 1 changed MDX files")
183183

184184
// Verify output contains agent metadata
185185
assert.Contains(t, outputStr, "NRJavaAgent")
@@ -331,7 +331,7 @@ func TestRunAgentFlow_SendMetadataFails(t *testing.T) {
331331

332332
func TestRunDocsFlow_LoadMetadataError(t *testing.T) {
333333
originalFunc := github.GetChangedMDXFilesFunc
334-
github.GetChangedMDXFilesFunc = func() ([]string, error) {
334+
github.GetChangedMDXFilesFunc = func(ctx context.Context) ([]string, error) {
335335
return nil, assert.AnError
336336
}
337337
defer func() {
@@ -350,7 +350,7 @@ func TestRunDocsFlow_LoadMetadataError(t *testing.T) {
350350

351351
func TestRunDocsFlow_NoMetadataChanges(t *testing.T) {
352352
originalFunc := github.GetChangedMDXFilesFunc
353-
github.GetChangedMDXFilesFunc = func() ([]string, error) {
353+
github.GetChangedMDXFilesFunc = func(ctx context.Context) ([]string, error) {
354354
return []string{}, nil
355355
}
356356
defer func() {
@@ -399,7 +399,7 @@ version: 1.3.1
399399
require.NoError(t, os.WriteFile(testMDXFile2, []byte(mdxContent2), 0644))
400400

401401
originalFunc := github.GetChangedMDXFilesFunc
402-
github.GetChangedMDXFilesFunc = func() ([]string, error) {
402+
github.GetChangedMDXFilesFunc = func(ctx context.Context) ([]string, error) {
403403
return []string{testMDXFile1, testMDXFile2}, nil
404404
}
405405
defer func() {
@@ -422,7 +422,7 @@ version: 1.3.1
422422
outputStr := getStdout()
423423

424424
assert.Contains(t, outputStr, "Successfully sent 1 of 2 metadata entries")
425-
assert.Contains(t, outputStr, "::warn::Failed to send metadata")
425+
assert.Contains(t, outputStr, "::error::Failed to send metadata")
426426
}
427427

428428
func TestRunAgentFlow_AgentControlDefinitionsError(t *testing.T) {
@@ -587,7 +587,7 @@ func TestRunAgentFlow_SigningSuccess_SingleArtifact(t *testing.T) {
587587

588588
// Mock OCI handler to return 1 successful upload
589589
originalOCIHandler := ociHandleUploadsFunc
590-
ociHandleUploadsFunc = func(cfg *models.OCIConfig, workspace, agentType, version string) ([]models.ArtifactUploadResult, error) {
590+
ociHandleUploadsFunc = func(ctx context.Context, cfg *models.OCIConfig, workspace, agentType, version string) ([]models.ArtifactUploadResult, error) {
591591
return []models.ArtifactUploadResult{
592592
createSuccessfulUploadResult("linux-tar", "sha256:abc123", "v1.2.3-linux-amd64"),
593593
}, nil
@@ -666,7 +666,7 @@ func TestRunAgentFlow_SigningDisabled_OCINotEnabled(t *testing.T) {
666666

667667
// Mock OCI handler should not be called since OCI is disabled
668668
originalOCIHandler := ociHandleUploadsFunc
669-
ociHandleUploadsFunc = func(cfg *models.OCIConfig, workspace, agentType, version string) ([]models.ArtifactUploadResult, error) {
669+
ociHandleUploadsFunc = func(ctx context.Context, cfg *models.OCIConfig, workspace, agentType, version string) ([]models.ArtifactUploadResult, error) {
670670
return []models.ArtifactUploadResult{}, nil
671671
}
672672
defer func() { ociHandleUploadsFunc = originalOCIHandler }()
@@ -720,7 +720,7 @@ func TestRunAgentFlow_SigningSkipped_AllUploadsFailed(t *testing.T) {
720720

721721
// Mock OCI handler to return only failed uploads
722722
originalOCIHandler := ociHandleUploadsFunc
723-
ociHandleUploadsFunc = func(cfg *models.OCIConfig, workspace, agentType, version string) ([]models.ArtifactUploadResult, error) {
723+
ociHandleUploadsFunc = func(ctx context.Context, cfg *models.OCIConfig, workspace, agentType, version string) ([]models.ArtifactUploadResult, error) {
724724
return []models.ArtifactUploadResult{
725725
createFailedUploadResult("linux-tar"),
726726
createFailedUploadResult("windows-zip"),
@@ -779,7 +779,7 @@ func TestRunAgentFlow_SigningError_ServiceFailure(t *testing.T) {
779779

780780
// Mock OCI handler to return 1 successful upload
781781
originalOCIHandler := ociHandleUploadsFunc
782-
ociHandleUploadsFunc = func(cfg *models.OCIConfig, workspace, agentType, version string) ([]models.ArtifactUploadResult, error) {
782+
ociHandleUploadsFunc = func(ctx context.Context, cfg *models.OCIConfig, workspace, agentType, version string) ([]models.ArtifactUploadResult, error) {
783783
return []models.ArtifactUploadResult{
784784
createSuccessfulUploadResult("linux-tar", "sha256:abc123", "v1.2.3-linux-amd64"),
785785
}, nil

0 commit comments

Comments
 (0)