Skip to content

Commit

Permalink
NR-318119 adding the ability to collect the logs sent to stdout and s…
Browse files Browse the repository at this point in the history
…tderr
  • Loading branch information
mbruin-NR committed Oct 15, 2024
1 parent f083abf commit d7de1c2
Show file tree
Hide file tree
Showing 13 changed files with 207 additions and 8 deletions.
10 changes: 10 additions & 0 deletions Agent.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1503,6 +1503,9 @@
F88C2CF72A2FA7AC00373EFE /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = F88C2CE32A2FA7AC00373EFE /* PrivacyInfo.xcprivacy */; };
F89167372BC9D1270085BCFC /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = F88C2CE32A2FA7AC00373EFE /* PrivacyInfo.xcprivacy */; };
F89167442BCD8EA30085BCFC /* NRViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = F824A43029AEAD63000886A6 /* NRViewModifier.swift */; };
F891A6BE2CB6F5C1007675F4 /* NRAutoLogCollector.m in Sources */ = {isa = PBXBuildFile; fileRef = F891A6BD2CB6F5C1007675F4 /* NRAutoLogCollector.m */; };
F891A6BF2CB6F5C1007675F4 /* NRAutoLogCollector.m in Sources */ = {isa = PBXBuildFile; fileRef = F891A6BD2CB6F5C1007675F4 /* NRAutoLogCollector.m */; };
F891A6C02CB6F5C1007675F4 /* NRAutoLogCollector.m in Sources */ = {isa = PBXBuildFile; fileRef = F891A6BD2CB6F5C1007675F4 /* NRAutoLogCollector.m */; };
F8A455482AFBE31E0057B1E0 /* NRMAURLSessionHeaderTrackingTestsOldEventSystem.m in Sources */ = {isa = PBXBuildFile; fileRef = F8A455472AFBE31E0057B1E0 /* NRMAURLSessionHeaderTrackingTestsOldEventSystem.m */; };
F8A455492AFBE31E0057B1E0 /* NRMAURLSessionHeaderTrackingTestsOldEventSystem.m in Sources */ = {isa = PBXBuildFile; fileRef = F8A455472AFBE31E0057B1E0 /* NRMAURLSessionHeaderTrackingTestsOldEventSystem.m */; };
F8AC3E932938FD6C002B4AA8 /* NRMAFakeDataHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = F8AC3E922938FD6C002B4AA8 /* NRMAFakeDataHelper.m */; };
Expand Down Expand Up @@ -2469,6 +2472,8 @@
F8728E562ACC9F840056F641 /* NRMANetworkMonitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NRMANetworkMonitor.h; sourceTree = "<group>"; };
F88C2CE32A2FA7AC00373EFE /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
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; };
F891A6BD2CB6F5C1007675F4 /* NRAutoLogCollector.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NRAutoLogCollector.m; sourceTree = "<group>"; };
F891A6D42CB6F5D8007675F4 /* NRAutoLogCollector.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NRAutoLogCollector.h; sourceTree = "<group>"; };
F8A455472AFBE31E0057B1E0 /* NRMAURLSessionHeaderTrackingTestsOldEventSystem.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NRMAURLSessionHeaderTrackingTestsOldEventSystem.m; sourceTree = "<group>"; };
F8AC3E922938FD6C002B4AA8 /* NRMAFakeDataHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = NRMAFakeDataHelper.m; sourceTree = "<group>"; };
F8AC3EA72938FDDB002B4AA8 /* NRMAFakeDataHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NRMAFakeDataHelper.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3641,6 +3646,8 @@
02FF4A8924DC652D00115469 /* NewRelicInternalUtils.h */,
02FF4A9024DC652D00115469 /* NewRelicInternalUtils.m */,
02FF4A7E24DC652B00115469 /* NRLogger.m */,
F891A6D42CB6F5D8007675F4 /* NRAutoLogCollector.h */,
F891A6BD2CB6F5C1007675F4 /* NRAutoLogCollector.m */,
02FF4A8224DC652C00115469 /* NRMAAppToken.h */,
02FF4A8E24DC652D00115469 /* NRMAAppToken.m */,
02FF4A8724DC652C00115469 /* NRMABase64.h */,
Expand Down Expand Up @@ -5388,6 +5395,7 @@
02FF48F424DC623400115469 /* NRMAExceptionMetaDataStore.m in Sources */,
02FF4A2124DC648600115469 /* NRMASummaryMeasurementConsumer.m in Sources */,
02FF482924DB5BA500115469 /* NRMAFlags.m in Sources */,
F891A6BE2CB6F5C1007675F4 /* NRAutoLogCollector.m in Sources */,
02FF48D924DC622700115469 /* NRMACrashDataWriter.m in Sources */,
02FF487224DC618F00115469 /* NRGCDOverride.m in Sources */,
02FF494D24DC626E00115469 /* NRMAHarvesterConnection.m in Sources */,
Expand Down Expand Up @@ -5558,6 +5566,7 @@
02FF4BF424E3201400115469 /* NRMAScopedHTTPTransactionMeasurement.m in Sources */,
02FF4BF524E3201400115469 /* NRMAMethodSwizzling.m in Sources */,
02FF4BF624E3201400115469 /* NRMACrashReporterRecorder.m in Sources */,
F891A6BF2CB6F5C1007675F4 /* NRAutoLogCollector.m in Sources */,
02FF4BF724E3201400115469 /* NRMACrashReport_CodeType.m in Sources */,
02FF4BF824E3201400115469 /* NRMAHarvestableHTTPTransaction.m in Sources */,
02FF4BF924E3201400115469 /* NRMAHarvestableActivity.m in Sources */,
Expand Down Expand Up @@ -5917,6 +5926,7 @@
3482325C2BC5F16E0070FAC3 /* NRMACPUVitals.m in Sources */,
348232502BC5F14D0070FAC3 /* NRMADEBUG_Reachability.m in Sources */,
348233532BC5F2780070FAC3 /* NRMAThread.m in Sources */,
F891A6C02CB6F5C1007675F4 /* NRAutoLogCollector.m in Sources */,
3482325D2BC5F16E0070FAC3 /* NRMABool.m in Sources */,
348233212BC5F2270070FAC3 /* NRMACrashReport_CodeType.m in Sources */,
3482328B2BC5F1B30070FAC3 /* NRMAMetric.m in Sources */,
Expand Down
2 changes: 2 additions & 0 deletions Agent/FeatureFlags/NRMAFlags.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@

+ (BOOL) shouldEnableBackgroundReporting;

+ (BOOL) shouldEnableRedirectStdOut;

+ (NSArray<NSString*>*) namesForFlags:(NRMAFeatureFlags)flags;

// Private Setting
Expand Down
7 changes: 7 additions & 0 deletions Agent/FeatureFlags/NRMAFlags.m
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,10 @@ + (BOOL) shouldEnableBackgroundReporting {
return ([NRMAFlags featureFlags] & NRFeatureFlag_BackgroundReporting) != 0;
}

+ (BOOL) shouldEnableRedirectStdOut {
return ([NRMAFlags featureFlags] & NRFeatureFlag_RedirectStdOut) != 0;
}

+ (NSArray<NSString*>*) namesForFlags:(NRMAFeatureFlags)flags {
NSMutableArray *retArray = [NSMutableArray array];
if ((flags & NRFeatureFlag_InteractionTracing) == NRFeatureFlag_InteractionTracing) {
Expand Down Expand Up @@ -239,6 +243,9 @@ + (BOOL) shouldEnableBackgroundReporting {
if ((flags & NRFeatureFlag_BackgroundReporting) == NRFeatureFlag_BackgroundReporting) {
[retArray addObject:@"BackgroundReporting"];
}
if ((flags & NRFeatureFlag_RedirectStdOut) == NRFeatureFlag_RedirectStdOut) {
[retArray addObject:@"RedirectStdOut"];
}

return retArray;
}
Expand Down
5 changes: 5 additions & 0 deletions Agent/General/NewRelicAgentInternal.m
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
#import "NRMAStartTimer.h"
#import "NRMAUDIDManager.h"
#import "NRMASupportMetricHelper.h"
#import "NRAutoLogCollector.h"


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

self = [super init];
if (self) {

if ([NRMAFlags shouldEnableRedirectStdOut]) {
[NRAutoLogCollector redirectStandardOutputAndError];
}

// NOTE: BackgroundReporting is only enabled for iOS 13+.
#if !TARGET_OS_WATCH
Expand Down
3 changes: 3 additions & 0 deletions Agent/Public/NRLogger.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ withAttributes:(NSDictionary *)attributes;
withMessage:(NSString *)message
withAgentLogsOn:(BOOL)agentLogsOn;

+ (void) logMessage:(NSString *) message
withTimestamp:(NSNumber *) timestamp;

/*!
Configure the amount of information the New Relic agent outputs about its internal operation.
Expand Down
1 change: 1 addition & 0 deletions Agent/Public/NewRelicFeatureFlags.h
Original file line number Diff line number Diff line change
Expand Up @@ -102,5 +102,6 @@ typedef NS_OPTIONS(unsigned long long, NRMAFeatureFlags){
NRFeatureFlag_NewEventSystem = 1 << 20, // Disabled by default
NRFeatureFlag_OfflineStorage = 1 << 21, // Disabled by default
NRFeatureFlag_BackgroundReporting = 1 << 22, // Disabled by default
NRFeatureFlag_RedirectStdOut = 1 << 23, // Disabled by default

};
20 changes: 20 additions & 0 deletions Agent/Utilities/NRAutoLogCollector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//
// NRAutoLogCollector.h
// Agent
//
// Created by Mike Bruin on 10/9/24.
// Copyright © 2024 New Relic. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface NRAutoLogCollector : NSObject {


}

+ (void) redirectStandardOutputAndError;
+ (void) restoreStandardOutputAndError;
+ (void) readAndParseLogFile;

@end
117 changes: 117 additions & 0 deletions Agent/Utilities/NRAutoLogCollector.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
//
// NRAutoLogCollector.m
// Agent
//
// Created by Mike Bruin on 10/9/24.
// Copyright © 2024 New Relic. All rights reserved.
//

#import "NRAutoLogCollector.h"
#import "NRLogger.h"

int saved_stdout;
int saved_stderr;

@interface NRAutoLogCollector()

@end

@implementation NRAutoLogCollector

+ (NSURL *) logFileURL {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSArray<NSURL *> *urls = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
NSURL *logsDirectory = [urls firstObject];
return [logsDirectory URLByAppendingPathComponent:@"agent.log"];
}

+ (void) clearLogFile {
[[NSFileManager defaultManager] removeItemAtURL:[NRAutoLogCollector logFileURL] error:nil];

[NRAutoLogCollector redirectStandardOutputAndError];
}

+ (void) redirectStandardOutputAndError {
// Save the original stdout file descriptor
saved_stdout = dup(fileno(stdout));
saved_stderr = dup(fileno(stderr));

// Redirect stdout to the file
freopen([[NRAutoLogCollector logFileURL].path cStringUsingEncoding:NSUTF8StringEncoding], "a+", stdout);
freopen([[NRAutoLogCollector logFileURL].path cStringUsingEncoding:NSUTF8StringEncoding], "a+", stderr);
}

+ (void) restoreStandardOutputAndError {
[NRAutoLogCollector readAndParseLogFile];

// Restore the original stdout
dup2(saved_stdout, fileno(stdout));
dup2(saved_stderr, fileno(stderr));
close(saved_stdout);
close(saved_stderr);

}

+ (void) readAndParseLogFile {
fflush(stdout);
fflush(stderr);
// Check if the file exists
if (![[NSFileManager defaultManager] fileExistsAtPath:[NRAutoLogCollector logFileURL].path]) {
return;
}

// Read the file content into an NSString
NSError *error = nil;
NSString *fileContents = [NSString stringWithContentsOfFile:[NRAutoLogCollector logFileURL].path
encoding:NSUTF8StringEncoding
error:&error];
if (error) {
return;
} else if (fileContents.length == 0){
return;
}

[NRAutoLogCollector clearLogFile];

// Split the file contents into individual log entries
NSArray<NSString *> *newLogEntries = [fileContents componentsSeparatedByString:@"\n"];

// Process each log entry
for (NSString *logEntry in newLogEntries) {
if ([logEntry length] > 0) {
[NRLogger logMessage:logEntry withTimestamp:[NRAutoLogCollector extractTimestamp:logEntry]];
}
}
}

+ (BOOL) isValidTimestamp:(NSString *) timestampString {
// Check if the timestamp string can be converted to a double
double timestamp = [timestampString doubleValue];
return timestamp > 0;
}

+ (NSNumber *) extractTimestamp:(NSString *) inputString {
// Define the regular expression pattern to match the t: value
NSString *pattern = @"t:(\\d+\\.\\d+)";
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil];

// Find matches in the input string
NSTextCheckingResult *match = [regex firstMatchInString:inputString options:0 range:NSMakeRange(0, [inputString length])];

if (match) {
// Extract the matched timestamp value
NSRange timestampRange = [match rangeAtIndex:1];
NSString *timestampString = [inputString substringWithRange:timestampRange];

// Validate the timestamp
if ([NRAutoLogCollector isValidTimestamp:(timestampString)]) {
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
formatter.numberStyle = NSNumberFormatterDecimalStyle;
return [formatter numberFromString:timestampString];
}
}

return nil;
}

@end
25 changes: 18 additions & 7 deletions Agent/Utilities/NRLogger.m
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
#import "NRMAHarvestController.h"
#import "NRMAHarvesterConfiguration.h"
#import "NRMASupportMetricHelper.h"
#import "NRMAFlags.h"
#import "NRAutoLogCollector.h"

NRLogger *_nr_logger = nil;

#define kNRMAMaxLogUploadRetry 3

@interface NRLogger()
- (void)addLogMessage:(NSDictionary *)message;
- (void)addLogMessage:(NSDictionary *)message : (BOOL) agentLogs;
- (void)setLogLevels:(unsigned int)levels;
- (void)setRemoteLogLevel:(unsigned int)level;

Expand Down Expand Up @@ -122,6 +124,18 @@ + (void)log:(unsigned int)level
}
}

+ (void) logMessage:(NSString *) message withTimestamp:(NSNumber *) timestamp {
NRLogger *logger = [NRLogger logger];

if((timestamp <= 0) || (timestamp == nil)){
timestamp = [NSNumber numberWithLongLong: (long long)([[NSDate date] timeIntervalSince1970] * 1000.0)];
}
[logger addLogMessage:[NSDictionary dictionaryWithObjectsAndKeys:
timestamp, NRLogMessageTimestampKey,
message, NRLogMessageMessageKey,
nil] :TRUE];
}

+ (NRLogLevels) logLevels {
return [[NRLogger logger] logLevels];
}
Expand Down Expand Up @@ -253,7 +267,7 @@ - (void)dealloc {
- (void)addLogMessage:(NSDictionary *)message : (BOOL) agentLogsOn {
// The static method checks the log level before we get here.
dispatch_async(logQueue, ^{
if (self->logTargets & NRLogTargetConsole) {
if ((self->logTargets & NRLogTargetConsole) && (![NRMAFlags shouldEnableRedirectStdOut])) {
NSLog(@"NewRelic(%@,%p):\t%@:%@\t%@\n\t%@",
[NewRelicInternalUtils agentVersion],
[NSThread currentThread],
Expand Down Expand Up @@ -330,10 +344,7 @@ - (NSData*) jsonDictionary:(NSDictionary*)message {
entityGuid = @"";
}

NSDictionary *requiredAttributes = @{NRLogMessageLevelKey: [message objectForKey:NRLogMessageLevelKey], // 1
NRLogMessageFileKey: [[message objectForKey:NRLogMessageFileKey]stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""], // 2
NRLogMessageLineNumberKey: [message objectForKey:NRLogMessageLineNumberKey], // 3
NRLogMessageMethodKey: [[message objectForKey:NRLogMessageMethodKey]stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""], // 4
NSDictionary *requiredAttributes = @{
NRLogMessageTimestampKey: [message objectForKey:NRLogMessageTimestampKey], // 5
NRLogMessageMessageKey: [[message objectForKey:NRLogMessageMessageKey]stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""],// 6
NRLogMessageSessionIdKey: NRSessionId, // 7
Expand All @@ -346,7 +357,6 @@ - (NSData*) jsonDictionary:(NSDictionary*)message {


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

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

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

Expand Down
4 changes: 3 additions & 1 deletion Test Harness/NRTestApp/NRTestApp/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
NRMAFeatureFlags.NRFeatureFlag_OfflineStorage])
// Note: Disabled by default. Enable or disable (default) flag to enable background reporting.
// NewRelic.enableFeatures([NRMAFeatureFlags.NRFeatureFlag_BackgroundReporting])


// Note: Disabled by default. Enable or disable (default) flag to enable auto collect of stdout.
// NewRelic.enableFeatures([NRMAFeatureFlags.NRFeatureFlag_RedirectStdOut])
NewRelic.saltDeviceUUID(true)

// NewRelic.replaceDeviceIdentifier("myDeviceId")
Expand Down
2 changes: 2 additions & 0 deletions Test Harness/NRTestApp/NRTestApp/Helpers/triggerException.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@
@interface triggerException : NSObject

+ (void) testing;
+ (void) testNSLog;

@end
4 changes: 4 additions & 0 deletions Test Harness/NRTestApp/NRTestApp/Helpers/triggerException.m
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ + (void) testing {
}
}

+ (void) testNSLog {
NSLog(@"TEST Objective C!!!!!");
}

@end
15 changes: 15 additions & 0 deletions Test Harness/NRTestApp/NRTestApp/ViewModels/UtilViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Foundation
import NewRelic
import OSLog

struct UtilOption {
let title:String
Expand Down Expand Up @@ -51,6 +52,7 @@ class UtilViewModel {
options.append(UtilOption(title: "Notice Network Request", handler: { [self] in noticeNWRequest()}))
options.append(UtilOption(title: "Notice Network Failure", handler: { [self] in noticeFailedNWRequest()}))

options.append(UtilOption(title: "Test System Logs", handler: { [self] in testSystemLogs()}))
options.append(UtilOption(title: "Test Log Dict", handler: { [self] in testLogDict()}))
options.append(UtilOption(title: "Test Log Error", handler: { [self] in testLogError()}))
options.append(UtilOption(title: "Test Log Attributes", handler: { [self] in testLogAttributes()}))
Expand Down Expand Up @@ -156,6 +158,12 @@ class UtilViewModel {
])
}

func testSystemLogs() {
triggerException.testNSLog()
print("TEST swift!!!!!")
os_log("TEST OSLog!!!!!!!")
}

func testLogError() {
do {
try errorMethod()
Expand All @@ -182,6 +190,13 @@ class UtilViewModel {
func setBuild() {
NewRelic.setApplicationBuild("42")
}

func testHttpRequestError() {
let reqUrl = URL(string: "https://5fp8uw121j.execute-api.ap-northeast-1.amazonaws.com/api/500/0")!
let urlSession = URLSession(configuration: URLSession.shared.configuration, delegate: taskProcessor, delegateQueue: nil)
let task = urlSession.dataTask(with: reqUrl)
task.resume()
}

func doDataTask() {
let urlSession = URLSession(configuration: URLSession.shared.configuration, delegate: taskProcessor, delegateQueue: nil)
Expand Down

0 comments on commit d7de1c2

Please sign in to comment.