Skip to content

Commit d7de1c2

Browse files
committed
NR-318119 adding the ability to collect the logs sent to stdout and stderr
1 parent f083abf commit d7de1c2

File tree

13 files changed

+207
-8
lines changed

13 files changed

+207
-8
lines changed

Agent.xcodeproj/project.pbxproj

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1503,6 +1503,9 @@
15031503
F88C2CF72A2FA7AC00373EFE /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = F88C2CE32A2FA7AC00373EFE /* PrivacyInfo.xcprivacy */; };
15041504
F89167372BC9D1270085BCFC /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = F88C2CE32A2FA7AC00373EFE /* PrivacyInfo.xcprivacy */; };
15051505
F89167442BCD8EA30085BCFC /* NRViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F824A43029AEAD63000886A6 /* NRViewModifier.swift */; };
1506+
F891A6BE2CB6F5C1007675F4 /* NRAutoLogCollector.m in Sources */ = {isa = PBXBuildFile; fileRef = F891A6BD2CB6F5C1007675F4 /* NRAutoLogCollector.m */; };
1507+
F891A6BF2CB6F5C1007675F4 /* NRAutoLogCollector.m in Sources */ = {isa = PBXBuildFile; fileRef = F891A6BD2CB6F5C1007675F4 /* NRAutoLogCollector.m */; };
1508+
F891A6C02CB6F5C1007675F4 /* NRAutoLogCollector.m in Sources */ = {isa = PBXBuildFile; fileRef = F891A6BD2CB6F5C1007675F4 /* NRAutoLogCollector.m */; };
15061509
F8A455482AFBE31E0057B1E0 /* NRMAURLSessionHeaderTrackingTestsOldEventSystem.m in Sources */ = {isa = PBXBuildFile; fileRef = F8A455472AFBE31E0057B1E0 /* NRMAURLSessionHeaderTrackingTestsOldEventSystem.m */; };
15071510
F8A455492AFBE31E0057B1E0 /* NRMAURLSessionHeaderTrackingTestsOldEventSystem.m in Sources */ = {isa = PBXBuildFile; fileRef = F8A455472AFBE31E0057B1E0 /* NRMAURLSessionHeaderTrackingTestsOldEventSystem.m */; };
15081511
F8AC3E932938FD6C002B4AA8 /* NRMAFakeDataHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = F8AC3E922938FD6C002B4AA8 /* NRMAFakeDataHelper.m */; };
@@ -2469,6 +2472,8 @@
24692472
F8728E562ACC9F840056F641 /* NRMANetworkMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NRMANetworkMonitor.h; sourceTree = "<group>"; };
24702473
F88C2CE32A2FA7AC00373EFE /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
24712474
F89167382BC9D1540085BCFC /* libc++.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = "libc++.tbd"; path = "Platforms/WatchOS.platform/Developer/SDKs/WatchOS10.2.sdk/usr/lib/libc++.tbd"; sourceTree = DEVELOPER_DIR; };
2475+
F891A6BD2CB6F5C1007675F4 /* NRAutoLogCollector.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NRAutoLogCollector.m; sourceTree = "<group>"; };
2476+
F891A6D42CB6F5D8007675F4 /* NRAutoLogCollector.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NRAutoLogCollector.h; sourceTree = "<group>"; };
24722477
F8A455472AFBE31E0057B1E0 /* NRMAURLSessionHeaderTrackingTestsOldEventSystem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NRMAURLSessionHeaderTrackingTestsOldEventSystem.m; sourceTree = "<group>"; };
24732478
F8AC3E922938FD6C002B4AA8 /* NRMAFakeDataHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NRMAFakeDataHelper.m; sourceTree = "<group>"; };
24742479
F8AC3EA72938FDDB002B4AA8 /* NRMAFakeDataHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NRMAFakeDataHelper.h; sourceTree = "<group>"; };
@@ -3641,6 +3646,8 @@
36413646
02FF4A8924DC652D00115469 /* NewRelicInternalUtils.h */,
36423647
02FF4A9024DC652D00115469 /* NewRelicInternalUtils.m */,
36433648
02FF4A7E24DC652B00115469 /* NRLogger.m */,
3649+
F891A6D42CB6F5D8007675F4 /* NRAutoLogCollector.h */,
3650+
F891A6BD2CB6F5C1007675F4 /* NRAutoLogCollector.m */,
36443651
02FF4A8224DC652C00115469 /* NRMAAppToken.h */,
36453652
02FF4A8E24DC652D00115469 /* NRMAAppToken.m */,
36463653
02FF4A8724DC652C00115469 /* NRMABase64.h */,
@@ -5388,6 +5395,7 @@
53885395
02FF48F424DC623400115469 /* NRMAExceptionMetaDataStore.m in Sources */,
53895396
02FF4A2124DC648600115469 /* NRMASummaryMeasurementConsumer.m in Sources */,
53905397
02FF482924DB5BA500115469 /* NRMAFlags.m in Sources */,
5398+
F891A6BE2CB6F5C1007675F4 /* NRAutoLogCollector.m in Sources */,
53915399
02FF48D924DC622700115469 /* NRMACrashDataWriter.m in Sources */,
53925400
02FF487224DC618F00115469 /* NRGCDOverride.m in Sources */,
53935401
02FF494D24DC626E00115469 /* NRMAHarvesterConnection.m in Sources */,
@@ -5558,6 +5566,7 @@
55585566
02FF4BF424E3201400115469 /* NRMAScopedHTTPTransactionMeasurement.m in Sources */,
55595567
02FF4BF524E3201400115469 /* NRMAMethodSwizzling.m in Sources */,
55605568
02FF4BF624E3201400115469 /* NRMACrashReporterRecorder.m in Sources */,
5569+
F891A6BF2CB6F5C1007675F4 /* NRAutoLogCollector.m in Sources */,
55615570
02FF4BF724E3201400115469 /* NRMACrashReport_CodeType.m in Sources */,
55625571
02FF4BF824E3201400115469 /* NRMAHarvestableHTTPTransaction.m in Sources */,
55635572
02FF4BF924E3201400115469 /* NRMAHarvestableActivity.m in Sources */,
@@ -5917,6 +5926,7 @@
59175926
3482325C2BC5F16E0070FAC3 /* NRMACPUVitals.m in Sources */,
59185927
348232502BC5F14D0070FAC3 /* NRMADEBUG_Reachability.m in Sources */,
59195928
348233532BC5F2780070FAC3 /* NRMAThread.m in Sources */,
5929+
F891A6C02CB6F5C1007675F4 /* NRAutoLogCollector.m in Sources */,
59205930
3482325D2BC5F16E0070FAC3 /* NRMABool.m in Sources */,
59215931
348233212BC5F2270070FAC3 /* NRMACrashReport_CodeType.m in Sources */,
59225932
3482328B2BC5F1B30070FAC3 /* NRMAMetric.m in Sources */,

Agent/FeatureFlags/NRMAFlags.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@
5555

5656
+ (BOOL) shouldEnableBackgroundReporting;
5757

58+
+ (BOOL) shouldEnableRedirectStdOut;
59+
5860
+ (NSArray<NSString*>*) namesForFlags:(NRMAFeatureFlags)flags;
5961

6062
// Private Setting

Agent/FeatureFlags/NRMAFlags.m

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,10 @@ + (BOOL) shouldEnableBackgroundReporting {
177177
return ([NRMAFlags featureFlags] & NRFeatureFlag_BackgroundReporting) != 0;
178178
}
179179

180+
+ (BOOL) shouldEnableRedirectStdOut {
181+
return ([NRMAFlags featureFlags] & NRFeatureFlag_RedirectStdOut) != 0;
182+
}
183+
180184
+ (NSArray<NSString*>*) namesForFlags:(NRMAFeatureFlags)flags {
181185
NSMutableArray *retArray = [NSMutableArray array];
182186
if ((flags & NRFeatureFlag_InteractionTracing) == NRFeatureFlag_InteractionTracing) {
@@ -239,6 +243,9 @@ + (BOOL) shouldEnableBackgroundReporting {
239243
if ((flags & NRFeatureFlag_BackgroundReporting) == NRFeatureFlag_BackgroundReporting) {
240244
[retArray addObject:@"BackgroundReporting"];
241245
}
246+
if ((flags & NRFeatureFlag_RedirectStdOut) == NRFeatureFlag_RedirectStdOut) {
247+
[retArray addObject:@"RedirectStdOut"];
248+
}
242249

243250
return retArray;
244251
}

Agent/General/NewRelicAgentInternal.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
#import "NRMAStartTimer.h"
5454
#import "NRMAUDIDManager.h"
5555
#import "NRMASupportMetricHelper.h"
56+
#import "NRAutoLogCollector.h"
5657

5758

5859
// Support for teardown and re-setup of the agent within a process lifetime for our test harness
@@ -159,6 +160,10 @@ - (id) initWithCollectorAddress:(NSString*)collectorHost
159160

160161
self = [super init];
161162
if (self) {
163+
164+
if ([NRMAFlags shouldEnableRedirectStdOut]) {
165+
[NRAutoLogCollector redirectStandardOutputAndError];
166+
}
162167

163168
// NOTE: BackgroundReporting is only enabled for iOS 13+.
164169
#if !TARGET_OS_WATCH

Agent/Public/NRLogger.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ withAttributes:(NSDictionary *)attributes;
138138
withMessage:(NSString *)message
139139
withAgentLogsOn:(BOOL)agentLogsOn;
140140

141+
+ (void) logMessage:(NSString *) message
142+
withTimestamp:(NSNumber *) timestamp;
143+
141144
/*!
142145
Configure the amount of information the New Relic agent outputs about its internal operation.
143146

Agent/Public/NewRelicFeatureFlags.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,5 +102,6 @@ typedef NS_OPTIONS(unsigned long long, NRMAFeatureFlags){
102102
NRFeatureFlag_NewEventSystem = 1 << 20, // Disabled by default
103103
NRFeatureFlag_OfflineStorage = 1 << 21, // Disabled by default
104104
NRFeatureFlag_BackgroundReporting = 1 << 22, // Disabled by default
105+
NRFeatureFlag_RedirectStdOut = 1 << 23, // Disabled by default
105106

106107
};

Agent/Utilities/NRAutoLogCollector.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
//
2+
// NRAutoLogCollector.h
3+
// Agent
4+
//
5+
// Created by Mike Bruin on 10/9/24.
6+
// Copyright © 2024 New Relic. All rights reserved.
7+
//
8+
9+
#import <Foundation/Foundation.h>
10+
11+
@interface NRAutoLogCollector : NSObject {
12+
13+
14+
}
15+
16+
+ (void) redirectStandardOutputAndError;
17+
+ (void) restoreStandardOutputAndError;
18+
+ (void) readAndParseLogFile;
19+
20+
@end

Agent/Utilities/NRAutoLogCollector.m

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//
2+
// NRAutoLogCollector.m
3+
// Agent
4+
//
5+
// Created by Mike Bruin on 10/9/24.
6+
// Copyright © 2024 New Relic. All rights reserved.
7+
//
8+
9+
#import "NRAutoLogCollector.h"
10+
#import "NRLogger.h"
11+
12+
int saved_stdout;
13+
int saved_stderr;
14+
15+
@interface NRAutoLogCollector()
16+
17+
@end
18+
19+
@implementation NRAutoLogCollector
20+
21+
+ (NSURL *) logFileURL {
22+
NSFileManager *fileManager = [NSFileManager defaultManager];
23+
NSArray<NSURL *> *urls = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
24+
NSURL *logsDirectory = [urls firstObject];
25+
return [logsDirectory URLByAppendingPathComponent:@"agent.log"];
26+
}
27+
28+
+ (void) clearLogFile {
29+
[[NSFileManager defaultManager] removeItemAtURL:[NRAutoLogCollector logFileURL] error:nil];
30+
31+
[NRAutoLogCollector redirectStandardOutputAndError];
32+
}
33+
34+
+ (void) redirectStandardOutputAndError {
35+
// Save the original stdout file descriptor
36+
saved_stdout = dup(fileno(stdout));
37+
saved_stderr = dup(fileno(stderr));
38+
39+
// Redirect stdout to the file
40+
freopen([[NRAutoLogCollector logFileURL].path cStringUsingEncoding:NSUTF8StringEncoding], "a+", stdout);
41+
freopen([[NRAutoLogCollector logFileURL].path cStringUsingEncoding:NSUTF8StringEncoding], "a+", stderr);
42+
}
43+
44+
+ (void) restoreStandardOutputAndError {
45+
[NRAutoLogCollector readAndParseLogFile];
46+
47+
// Restore the original stdout
48+
dup2(saved_stdout, fileno(stdout));
49+
dup2(saved_stderr, fileno(stderr));
50+
close(saved_stdout);
51+
close(saved_stderr);
52+
53+
}
54+
55+
+ (void) readAndParseLogFile {
56+
fflush(stdout);
57+
fflush(stderr);
58+
// Check if the file exists
59+
if (![[NSFileManager defaultManager] fileExistsAtPath:[NRAutoLogCollector logFileURL].path]) {
60+
return;
61+
}
62+
63+
// Read the file content into an NSString
64+
NSError *error = nil;
65+
NSString *fileContents = [NSString stringWithContentsOfFile:[NRAutoLogCollector logFileURL].path
66+
encoding:NSUTF8StringEncoding
67+
error:&error];
68+
if (error) {
69+
return;
70+
} else if (fileContents.length == 0){
71+
return;
72+
}
73+
74+
[NRAutoLogCollector clearLogFile];
75+
76+
// Split the file contents into individual log entries
77+
NSArray<NSString *> *newLogEntries = [fileContents componentsSeparatedByString:@"\n"];
78+
79+
// Process each log entry
80+
for (NSString *logEntry in newLogEntries) {
81+
if ([logEntry length] > 0) {
82+
[NRLogger logMessage:logEntry withTimestamp:[NRAutoLogCollector extractTimestamp:logEntry]];
83+
}
84+
}
85+
}
86+
87+
+ (BOOL) isValidTimestamp:(NSString *) timestampString {
88+
// Check if the timestamp string can be converted to a double
89+
double timestamp = [timestampString doubleValue];
90+
return timestamp > 0;
91+
}
92+
93+
+ (NSNumber *) extractTimestamp:(NSString *) inputString {
94+
// Define the regular expression pattern to match the t: value
95+
NSString *pattern = @"t:(\\d+\\.\\d+)";
96+
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil];
97+
98+
// Find matches in the input string
99+
NSTextCheckingResult *match = [regex firstMatchInString:inputString options:0 range:NSMakeRange(0, [inputString length])];
100+
101+
if (match) {
102+
// Extract the matched timestamp value
103+
NSRange timestampRange = [match rangeAtIndex:1];
104+
NSString *timestampString = [inputString substringWithRange:timestampRange];
105+
106+
// Validate the timestamp
107+
if ([NRAutoLogCollector isValidTimestamp:(timestampString)]) {
108+
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
109+
formatter.numberStyle = NSNumberFormatterDecimalStyle;
110+
return [formatter numberFromString:timestampString];
111+
}
112+
}
113+
114+
return nil;
115+
}
116+
117+
@end

Agent/Utilities/NRLogger.m

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@
1313
#import "NRMAHarvestController.h"
1414
#import "NRMAHarvesterConfiguration.h"
1515
#import "NRMASupportMetricHelper.h"
16+
#import "NRMAFlags.h"
17+
#import "NRAutoLogCollector.h"
1618

1719
NRLogger *_nr_logger = nil;
1820

1921
#define kNRMAMaxLogUploadRetry 3
2022

2123
@interface NRLogger()
22-
- (void)addLogMessage:(NSDictionary *)message;
24+
- (void)addLogMessage:(NSDictionary *)message : (BOOL) agentLogs;
2325
- (void)setLogLevels:(unsigned int)levels;
2426
- (void)setRemoteLogLevel:(unsigned int)level;
2527

@@ -122,6 +124,18 @@ + (void)log:(unsigned int)level
122124
}
123125
}
124126

127+
+ (void) logMessage:(NSString *) message withTimestamp:(NSNumber *) timestamp {
128+
NRLogger *logger = [NRLogger logger];
129+
130+
if((timestamp <= 0) || (timestamp == nil)){
131+
timestamp = [NSNumber numberWithLongLong: (long long)([[NSDate date] timeIntervalSince1970] * 1000.0)];
132+
}
133+
[logger addLogMessage:[NSDictionary dictionaryWithObjectsAndKeys:
134+
timestamp, NRLogMessageTimestampKey,
135+
message, NRLogMessageMessageKey,
136+
nil] :TRUE];
137+
}
138+
125139
+ (NRLogLevels) logLevels {
126140
return [[NRLogger logger] logLevels];
127141
}
@@ -253,7 +267,7 @@ - (void)dealloc {
253267
- (void)addLogMessage:(NSDictionary *)message : (BOOL) agentLogsOn {
254268
// The static method checks the log level before we get here.
255269
dispatch_async(logQueue, ^{
256-
if (self->logTargets & NRLogTargetConsole) {
270+
if ((self->logTargets & NRLogTargetConsole) && (![NRMAFlags shouldEnableRedirectStdOut])) {
257271
NSLog(@"NewRelic(%@,%p):\t%@:%@\t%@\n\t%@",
258272
[NewRelicInternalUtils agentVersion],
259273
[NSThread currentThread],
@@ -330,10 +344,7 @@ - (NSData*) jsonDictionary:(NSDictionary*)message {
330344
entityGuid = @"";
331345
}
332346

333-
NSDictionary *requiredAttributes = @{NRLogMessageLevelKey: [message objectForKey:NRLogMessageLevelKey], // 1
334-
NRLogMessageFileKey: [[message objectForKey:NRLogMessageFileKey]stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""], // 2
335-
NRLogMessageLineNumberKey: [message objectForKey:NRLogMessageLineNumberKey], // 3
336-
NRLogMessageMethodKey: [[message objectForKey:NRLogMessageMethodKey]stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""], // 4
347+
NSDictionary *requiredAttributes = @{
337348
NRLogMessageTimestampKey: [message objectForKey:NRLogMessageTimestampKey], // 5
338349
NRLogMessageMessageKey: [[message objectForKey:NRLogMessageMessageKey]stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""],// 6
339350
NRLogMessageSessionIdKey: NRSessionId, // 7
@@ -346,7 +357,6 @@ - (NSData*) jsonDictionary:(NSDictionary*)message {
346357

347358

348359
NSMutableDictionary *providedAttributes = [message mutableCopy];
349-
[providedAttributes removeObjectsForKeys:@[NRLogMessageLevelKey,NRLogMessageFileKey,NRLogMessageLineNumberKey,NRLogMessageMethodKey,NRLogMessageTimestampKey,NRLogMessageMessageKey]];
350360
[providedAttributes addEntriesFromDictionary:requiredAttributes];
351361
NSError* error = nil;
352362

@@ -511,6 +521,7 @@ - (void)setLogURL:(NSString*)url {
511521

512522
// Enqueue an upload task for this specific logData , represented by the "formattedData" below.
513523
- (void)enqueueLogUpload {
524+
[NRAutoLogCollector readAndParseLogFile];
514525
@synchronized(self) {
515526
if (self->logFile) {
516527

Test Harness/NRTestApp/NRTestApp/AppDelegate.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
3333
NRMAFeatureFlags.NRFeatureFlag_OfflineStorage])
3434
// Note: Disabled by default. Enable or disable (default) flag to enable background reporting.
3535
// NewRelic.enableFeatures([NRMAFeatureFlags.NRFeatureFlag_BackgroundReporting])
36-
36+
37+
// Note: Disabled by default. Enable or disable (default) flag to enable auto collect of stdout.
38+
// NewRelic.enableFeatures([NRMAFeatureFlags.NRFeatureFlag_RedirectStdOut])
3739
NewRelic.saltDeviceUUID(true)
3840

3941
// NewRelic.replaceDeviceIdentifier("myDeviceId")

0 commit comments

Comments
 (0)