-
-
Notifications
You must be signed in to change notification settings - Fork 371
Structured Logs: Collect stdout/stderr
#6441
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 5 commits
833ee24
60de7e4
e5f4284
3b1fac8
17e96a7
da2e360
d591b0e
6dfb50f
d75f26c
73a3bf3
1b8ada0
99c3dfe
4d10782
e317e53
0b164a9
4d79ab7
5ea5d93
44062ff
441a879
629ee22
02ffb6c
87239f4
78af4be
ff3a6ca
0169ba4
005efa3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,168 @@ | ||
| #import "SentryStdOutLogIntegration.h" | ||
| #import "SentryDependencyContainer.h" | ||
|
Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m
|
||
| #import "SentryLogC.h" | ||
| #import "SentryOptions.h" | ||
| #import "SentrySwift.h" | ||
| #import <Foundation/Foundation.h> | ||
| #import <stdatomic.h> | ||
|
|
||
| @interface SentryStdOutLogIntegration () | ||
|
|
||
| @property (strong, nonatomic) NSPipe *stdErrPipe; | ||
| @property (strong, nonatomic) NSPipe *stdOutPipe; | ||
| @property (nonatomic, copy) void (^logHandler)(NSData *, BOOL isStderr); | ||
| @property (nonatomic, assign) int originalStdOut; | ||
| @property (nonatomic, assign) int originalStdErr; | ||
| @property (strong, nonatomic, nullable) SentryLogger *injectedLogger; | ||
| @property (strong, nonatomic, nullable) SentryDispatchFactory *injectedDispatchFactory; | ||
| @property (strong, nonatomic, nullable) SentryDispatchQueueWrapper *dispatchQueueWrapper; | ||
|
|
||
| @end | ||
|
|
||
| // Global atomic flag for infinite loop protection | ||
| static _Atomic bool _isForwardingLogs = false; | ||
|
|
||
| @implementation SentryStdOutLogIntegration | ||
|
|
||
| - (instancetype)init:(SentryDispatchFactory *)dispatchFactory | ||
| { | ||
| return [self initWithDispatchFactory:dispatchFactory logger:nil]; | ||
| } | ||
|
|
||
| // Only for testing | ||
| - (instancetype)initWithDispatchFactory:(SentryDispatchFactory *)dispatchFactory | ||
| logger:(nullable SentryLogger *)logger | ||
| { | ||
| if (self = [super init]) { | ||
| self.injectedLogger = logger; | ||
| self.injectedDispatchFactory = dispatchFactory; | ||
| } | ||
| return self; | ||
| } | ||
|
|
||
| - (SentryLogger *)logger | ||
| { | ||
| return self.injectedLogger ?: SentrySDK.logger; | ||
| } | ||
|
|
||
| - (SentryDispatchFactory *)dispatchFactory | ||
| { | ||
| return self.injectedDispatchFactory ?: SentryDependencyContainer.sharedInstance.dispatchFactory; | ||
| } | ||
|
|
||
| - (BOOL)installWithOptions:(SentryOptions *)options | ||
| { | ||
| if (![super installWithOptions:options]) { | ||
| return NO; | ||
| } | ||
|
|
||
| // Only install if logs are enabled | ||
| if (!options.enableLogs) { | ||
| return NO; | ||
| } | ||
|
|
||
| self.dispatchQueueWrapper = | ||
| [self.dispatchFactory createUtilityQueue:"com.sentry.stdout_log_writing_queue" | ||
| relativePriority:-3]; | ||
|
|
||
| __weak typeof(self) weakSelf = self; | ||
| self.logHandler = ^(NSData *data, BOOL isStderr) { | ||
| __strong typeof(weakSelf) strongSelf = weakSelf; | ||
| if (!strongSelf) | ||
| return; | ||
|
|
||
| if (data && data.length > 0) { | ||
| NSString *logString = [[NSString alloc] initWithData:data | ||
| encoding:NSUTF8StringEncoding]; | ||
| if (logString) { | ||
| // Check global atomic flag to avoid infinite loops | ||
| if (atomic_exchange(&_isForwardingLogs, true)) { | ||
| return; // Already forwarding, break the loop. | ||
| } | ||
| NSDictionary *attributes = | ||
| @{ @"sentry.log.source" : isStderr ? @"stderr" : @"stdout" }; | ||
| if (isStderr) { | ||
| [strongSelf.logger warn:logString attributes:attributes]; | ||
| } else { | ||
| [strongSelf.logger info:logString attributes:attributes]; | ||
| } | ||
|
|
||
| // Clear global atomic flag | ||
| atomic_store(&_isForwardingLogs, false); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| [self start]; | ||
|
|
||
| return YES; | ||
| } | ||
|
|
||
| - (void)start | ||
| { | ||
| self.originalStdOut = dup(STDOUT_FILENO); | ||
| self.originalStdErr = dup(STDERR_FILENO); | ||
|
|
||
| self.stdOutPipe = [self duplicateFileDescriptor:STDOUT_FILENO isStderr:NO]; | ||
| self.stdErrPipe = [self duplicateFileDescriptor:STDERR_FILENO isStderr:YES]; | ||
| } | ||
|
|
||
| - (void)stop | ||
| { | ||
| if (self.stdOutPipe || self.stdErrPipe) { | ||
| // Restore original file descriptors | ||
| if (self.originalStdOut >= 0) { | ||
| dup2(self.originalStdOut, STDOUT_FILENO); | ||
| close(self.originalStdOut); | ||
| self.originalStdOut = -1; | ||
| } | ||
|
|
||
| if (self.originalStdErr >= 0) { | ||
| dup2(self.originalStdErr, STDERR_FILENO); | ||
| close(self.originalStdErr); | ||
| self.originalStdErr = -1; | ||
| } | ||
|
|
||
| // Clean up pipes | ||
| self.stdOutPipe.fileHandleForReading.readabilityHandler = nil; | ||
| self.stdErrPipe.fileHandleForReading.readabilityHandler = nil; | ||
|
|
||
| self.stdOutPipe = nil; | ||
| self.stdErrPipe = nil; | ||
| self.logHandler = nil; | ||
| } | ||
| } | ||
|
|
||
| - (void)uninstall | ||
| { | ||
| [self stop]; | ||
| } | ||
|
|
||
| // Write the input file descriptor to the input file handle, preserving the original output as well. | ||
| // This can be used to save stdout/stderr to a file while also keeping it on the console. | ||
| - (NSPipe *)duplicateFileDescriptor:(int)fileDescriptor isStderr:(BOOL)isStderr | ||
| { | ||
| NSPipe *pipe = [[NSPipe alloc] init]; | ||
| int newDescriptor = dup(fileDescriptor); | ||
| NSFileHandle *newFileHandle = [[NSFileHandle alloc] initWithFileDescriptor:newDescriptor | ||
| closeOnDealloc:YES]; | ||
|
|
||
| if (dup2(pipe.fileHandleForWriting.fileDescriptor, fileDescriptor) < 0) { | ||
| SENTRY_LOG_ERROR(@"Unable to duplicate file descriptor %d", fileDescriptor); | ||
| close(newDescriptor); | ||
| return nil; | ||
| } | ||
|
|
||
| __weak typeof(self) weakSelf = self; | ||
| pipe.fileHandleForReading.readabilityHandler = ^(NSFileHandle *handle) { | ||
| NSData *data = handle.availableData; | ||
| if (weakSelf.logHandler) { | ||
| weakSelf.logHandler(data, isStderr); | ||
| } | ||
| [weakSelf.dispatchQueueWrapper dispatchAsyncWithBlock:^{ [newFileHandle writeData:data]; }]; | ||
| }; | ||
|
|
||
| return pipe; | ||
| } | ||
|
|
||
| @end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| #import "SentryBaseIntegration.h" | ||
|
|
||
| NS_ASSUME_NONNULL_BEGIN | ||
|
|
||
| @class SentryLogger; | ||
| @class SentryDispatchFactory; | ||
| @class SentryDispatchQueueWrapper; | ||
|
|
||
| /** | ||
| * Integration that captures stdout and stderr output and forwards it to Sentry logs. | ||
| * This integration is automatically enabled when enableLogs is set to true. | ||
| */ | ||
| @interface SentryStdOutLogIntegration : SentryBaseIntegration | ||
|
|
||
| // Only for testing | ||
| - (instancetype)initWithDispatchFactory:(SentryDispatchFactory *)dispatchFactory | ||
| logger:(nullable SentryLogger *)logger; | ||
|
|
||
| @end | ||
|
|
||
| NS_ASSUME_NONNULL_END |
Uh oh!
There was an error while loading. Please reload this page.