Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
833ee24
Add automatic stdout log integration when `enableLogs` is set to true
denrase Oct 16, 2025
60de7e4
Merge branch 'main' into denrase/structured-logs-stdout
denrase Oct 16, 2025
e5f4284
update changelog
denrase Oct 16, 2025
3b1fac8
fix weak ref handling
denrase Oct 16, 2025
17e96a7
remove SentrySDKLog from logger
denrase Oct 16, 2025
da2e360
Merge branch 'main' into denrase/structured-logs-stdout
denrase Nov 10, 2025
d591b0e
remove dep container import (ported to swift)
denrase Nov 10, 2025
6dfb50f
add SentryStdOutLogIntegration as default
denrase Nov 10, 2025
d75f26c
filter out strings containing [Sentry]
denrase Nov 10, 2025
73a3bf3
Merge branch 'main' into denrase/structured-logs-stdout
denrase Nov 10, 2025
1b8ada0
update changelog
denrase Nov 10, 2025
99c3dfe
Merge branch 'main' into denrase/structured-logs-stdout
denrase Nov 12, 2025
4d10782
Merge branch 'main' into denrase/structured-logs-stdout
denrase Nov 13, 2025
e317e53
Merge branch 'main' into denrase/structured-logs-stdout
denrase Nov 17, 2025
0b164a9
don’t install directly. prepare to move part of code outside (not use…
denrase Nov 17, 2025
4d79ab7
Merge branch 'main' into denrase/structured-logs-stdout
denrase Nov 17, 2025
5ea5d93
move main implementation to swift (driver pattern)
denrase Nov 17, 2025
44062ff
Merge branch 'main' into denrase/structured-logs-stdout
denrase Nov 17, 2025
441a879
re-direct SentrySDKLog to default stdout
denrase Nov 17, 2025
629ee22
Merge branch 'main' into denrase/structured-logs-stdout
denrase Nov 17, 2025
02ffb6c
revert vendedUtilityQueueHandler
denrase Nov 17, 2025
87239f4
update changelog
denrase Nov 17, 2025
78af4be
fix cl merge
denrase Nov 17, 2025
ff3a6ca
make driver nullable
denrase Nov 17, 2025
0169ba4
Add experimental log capture
denrase Nov 18, 2025
005efa3
Merge branch 'main' into denrase/structured-logs-stdout
denrase Nov 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

- Add SentryDistribution as Swift Package Manager target (#6149)
- Add option `enablePropagateTraceparent` to support OTel/W3C trace propagation (#6356)
- Structured Logs: Collect `stdout/stderr` per default (#6441)

### Fixes

Expand Down
28 changes: 28 additions & 0 deletions Sentry.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,10 @@
92235CAC2E15369900865983 /* SentryLogBatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92235CAB2E15369900865983 /* SentryLogBatcher.swift */; };
92235CAE2E15549C00865983 /* SentryLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92235CAD2E15549C00865983 /* SentryLogger.swift */; };
92235CB02E155B2600865983 /* SentryLoggerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 92235CAF2E155B2600865983 /* SentryLoggerTests.swift */; };
9229D1462E9FF09E00FD09ED /* SentryStdOutLogIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9229D1452E9FF09E00FD09ED /* SentryStdOutLogIntegrationTests.swift */; };
925824C22CB5897700C9B20B /* SentrySessionReplayIntegration-Hybrid.h in Headers */ = {isa = PBXBuildFile; fileRef = D80382BE2C09C6FD0090E048 /* SentrySessionReplayIntegration-Hybrid.h */; settings = {ATTRIBUTES = (Private, ); }; };
925B67CC2EA11970005B2D3B /* SentryStdOutLogIntegration.h in Headers */ = {isa = PBXBuildFile; fileRef = 925B67CB2EA11970005B2D3B /* SentryStdOutLogIntegration.h */; };
925B67D02EA11B0E005B2D3B /* SentryStdoutLogIntegration.m in Sources */ = {isa = PBXBuildFile; fileRef = 925B67CF2EA11B0E005B2D3B /* SentryStdoutLogIntegration.m */; };
9264E1EB2E2E385E00B077CF /* SentryLogMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9264E1EA2E2E385B00B077CF /* SentryLogMessage.swift */; };
9264E1ED2E2E397C00B077CF /* SentryLogMessageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9264E1EC2E2E397400B077CF /* SentryLogMessageTests.swift */; };
92672BB629C9A2A9006B021C /* SentryBreadcrumb+Private.h in Headers */ = {isa = PBXBuildFile; fileRef = 92672BB529C9A2A9006B021C /* SentryBreadcrumb+Private.h */; settings = {ATTRIBUTES = (Private, ); }; };
Expand Down Expand Up @@ -2077,6 +2080,9 @@
92235CAB2E15369900865983 /* SentryLogBatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogBatcher.swift; sourceTree = "<group>"; };
92235CAD2E15549C00865983 /* SentryLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogger.swift; sourceTree = "<group>"; };
92235CAF2E155B2600865983 /* SentryLoggerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLoggerTests.swift; sourceTree = "<group>"; };
9229D1452E9FF09E00FD09ED /* SentryStdOutLogIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryStdOutLogIntegrationTests.swift; sourceTree = "<group>"; };
925B67CB2EA11970005B2D3B /* SentryStdOutLogIntegration.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SentryStdOutLogIntegration.h; path = include/SentryStdOutLogIntegration.h; sourceTree = "<group>"; };
925B67CF2EA11B0E005B2D3B /* SentryStdoutLogIntegration.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SentryStdoutLogIntegration.m; sourceTree = "<group>"; };
9264E1EA2E2E385B00B077CF /* SentryLogMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogMessage.swift; sourceTree = "<group>"; };
9264E1EC2E2E397400B077CF /* SentryLogMessageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SentryLogMessageTests.swift; sourceTree = "<group>"; };
92672BB529C9A2A9006B021C /* SentryBreadcrumb+Private.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SentryBreadcrumb+Private.h"; path = "include/HybridPublic/SentryBreadcrumb+Private.h"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2918,6 +2924,7 @@
D85596EF280580BE0041FF8B /* Screenshot */,
0A9BF4E028A114690068D266 /* ViewHierarchy */,
D80CD8D52B752FD9002F710B /* SessionReplay */,
925B67892EA118EA005B2D3B /* StdOutLog */,
FA034AC72DD3DB4900FE3107 /* SentryIntegrationProtocol.h */,
7BA235622600B61200E12865 /* SentryInternalNotificationNames.h */,
0A2D8D5C289815EB008720F6 /* SentryBaseIntegration.h */,
Expand Down Expand Up @@ -3515,6 +3522,7 @@
7BE0DC3F272AE9F0004FA8B7 /* Session */,
7BE0DC3E272AE9DC004FA8B7 /* SentryCrash */,
D80694C12B7CC85800B820E6 /* SessionReplay */,
9292AA712EA1110E005DF5E2 /* StdOutLog */,
7B59398324AB481B0003AAD2 /* NotificationCenterTestCase.swift */,
0A2D8D8628992260008720F6 /* SentryBaseIntegrationTests.swift */,
);
Expand Down Expand Up @@ -4222,6 +4230,23 @@
name = Transaction;
sourceTree = "<group>";
};
925B67892EA118EA005B2D3B /* StdOutLog */ = {
isa = PBXGroup;
children = (
925B67CB2EA11970005B2D3B /* SentryStdOutLogIntegration.h */,
925B67CF2EA11B0E005B2D3B /* SentryStdoutLogIntegration.m */,
);
name = StdOutLog;
sourceTree = "<group>";
};
9292AA712EA1110E005DF5E2 /* StdOutLog */ = {
isa = PBXGroup;
children = (
9229D1452E9FF09E00FD09ED /* SentryStdOutLogIntegrationTests.swift */,
);
path = StdOutLog;
sourceTree = "<group>";
};
D4009EA02D77196F0007AF30 /* ViewCapture */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -5205,6 +5230,7 @@
7BA61CC8247D125400C130A8 /* SentryDefaultThreadInspector.h in Headers */,
63FE713320DA4C1100CDBAE8 /* SentryCrashCPU.h in Headers */,
6271ADF32BA06D9B0098D2E9 /* SentryInternalSerializable.h in Headers */,
925B67CC2EA11970005B2D3B /* SentryStdOutLogIntegration.h in Headers */,
D8853C842833EABC00700D64 /* SentryANRTrackerV1.h in Headers */,
63FE715B20DA4C1100CDBAE8 /* SentryCrashSignalInfo.h in Headers */,
63FE70E520DA4C1000CDBAE8 /* SentryCrashMonitor_CPPException.h in Headers */,
Expand Down Expand Up @@ -5676,6 +5702,7 @@
84DBC62C2CE82F12000C4904 /* SentryFeedback.swift in Sources */,
F41362132E1C566100B84443 /* SentryScopePersistentStore+User.swift in Sources */,
63B818FA1EC34639002FDF4C /* SentryDebugMeta.m in Sources */,
925B67D02EA11B0E005B2D3B /* SentryStdoutLogIntegration.m in Sources */,
7B98D7D325FB65AE00C5A389 /* SentryWatchdogTerminationTracker.m in Sources */,
8E564AE8267AF22600FE117D /* SentryNetworkTrackingIntegration.m in Sources */,
63AA75EF1EB8B3C400D153DE /* SentryClient.m in Sources */,
Expand Down Expand Up @@ -6122,6 +6149,7 @@
8431EE5B29ADB8EA00D8DC56 /* SentryTimeTests.m in Sources */,
7B0A54562523178700A71716 /* SentryScopeSwiftTests.swift in Sources */,
7B5B94332657A816002E474B /* SentryAppStartTrackingIntegrationTests.swift in Sources */,
9229D1462E9FF09E00FD09ED /* SentryStdOutLogIntegrationTests.swift in Sources */,
62278CA82E30B21A0022ABC6 /* SentryHttpTransportFlushIntegrationTests.swift in Sources */,
0A5370A128A3EC2400B2DCDE /* SentryViewHierarchyProviderTests.swift in Sources */,
D8FFE50C2703DBB400607131 /* SwizzlingCallTests.swift in Sources */,
Expand Down
5 changes: 4 additions & 1 deletion SentryTestUtils/TestDispatchFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import Foundation
@_spi(Private) public class TestDispatchFactory: SentryDispatchFactory {
public var vendedSourceHandler: ((TestDispatchSourceWrapper) -> Void)?
public var vendedQueueHandler: ((TestSentryDispatchQueueWrapper) -> Void)?
public var vendedUtilityQueueHandler: ((TestSentryDispatchQueueWrapper) -> Void)?

public var createUtilityQueueInvocations = Invocations<(name: String, relativePriority: Int32)>()

Expand All @@ -18,7 +19,9 @@ import Foundation
createUtilityQueueInvocations.record((String(cString: name), relativePriority))
// Due to the absense of `dispatch_queue_attr_make_with_qos_class` in Swift, we do not pass any attributes.
// This will not affect the tests as they do not need an actual low priority queue.
return TestSentryDispatchQueueWrapper(name: name, attributes: nil)
let queue = TestSentryDispatchQueueWrapper(name: name, attributes: nil)
vendedUtilityQueueHandler?(queue)
return queue
}

public override func source(withInterval interval: Int, leeway: Int, queueName: UnsafePointer<CChar>, attributes: __OS_dispatch_queue_attr, eventHandler: @escaping () -> Void) -> SentryDispatchSourceWrapper {
Expand Down
168 changes: 168 additions & 0 deletions Sources/Sentry/SentryStdoutLogIntegration.m
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

View workflow job for this annotation

GitHub Actions / Lint

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Unit macOS 26 Sentry

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Sample watchOS-Swift WatchKit App Debug

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry' at path '/Users/runner/work/sentry-cocoa/sentry-cocoa/Sentry.xcodeproj')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Unit tvOS 26 Sentry

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Sample tvOS-Swift DebugV9

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry' at path '/Users/runner/work/sentry-cocoa/sentry-cocoa/Sentry.xcodeproj')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Check no UIKit linkage (DebugWithoutUIKit)

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Sample iOS-ObjectiveC DebugV9

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry' at path '/Users/runner/work/sentry-cocoa/sentry-cocoa/Sentry.xcodeproj')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Build SDK v9

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Check API Stability ()

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Unit iOS 16 Sentry

'SentryDependencyContainer.h' file not found

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Build with SPM

'SentryDependencyContainer.h' file not found (in target 'SentryObjc' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Unit macOS 15 Sentry

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Unit tvOS 18 Sentry

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Build XCFramework Slices (SentrySwiftUI, mh_dylib, sentry-swiftui) / appletvos

'SentryDependencyContainer.h' file not found

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Unit iOS 18 Sentry

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Build XCFramework Slices (Sentry, staticlib, sentry-static) / appletvos

'SentryDependencyContainer.h' file not found

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Unit tvOS 17 Sentry

'SentryDependencyContainer.h' file not found

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Unit Catalyst 14 Sentry

'SentryDependencyContainer.h' file not found

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Unit Catalyst 15 Sentry

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Check UIKit linkage (Debug)

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Check no UIKit linkage (ReleaseWithoutUIKit)

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Sample iOS-Swift DebugV9

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry' at path '/Users/runner/work/sentry-cocoa/sentry-cocoa/Sentry.xcodeproj')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Unit iOS 17 Sentry

'SentryDependencyContainer.h' file not found

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Unit iOS 17 SentrySwiftUI

'SentryDependencyContainer.h' file not found

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Check compiling Async Safe Logs

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Sample iOS-SwiftUI DebugV9

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry' at path '/Users/runner/work/sentry-cocoa/sentry-cocoa/Sentry.xcodeproj')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Unit macOS 14 Sentry

'SentryDependencyContainer.h' file not found

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Sample SessionReplay-CameraTest Debug

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry' at path '/Users/runner/work/sentry-cocoa/sentry-cocoa/Sentry.xcodeproj')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Check UIKit linkage (Release)

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Sample visionOS-Swift DebugV9

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry' at path '/Users/runner/work/sentry-cocoa/sentry-cocoa/Sentry.xcodeproj')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Sample macOS-Swift DebugV9

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry' at path '/Users/runner/work/sentry-cocoa/sentry-cocoa/Sentry.xcodeproj')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Sample macOS-SwiftUI Debug

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry' at path '/Users/runner/work/sentry-cocoa/sentry-cocoa/Sentry.xcodeproj')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Unit with Test Server macOS 15

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Analyze (cpp)

'SentryDependencyContainer.h' file not found

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Run SwiftUI Crash Test

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry' at path '/Users/runner/work/sentry-cocoa/sentry-cocoa/Sentry.xcodeproj')

Check failure on line 2 in Sources/Sentry/SentryStdoutLogIntegration.m

View workflow job for this annotation

GitHub Actions / Unit iOS 26 Sentry

'SentryDependencyContainer.h' file not found (in target 'Sentry' from project 'Sentry')
#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
3 changes: 2 additions & 1 deletion Sources/Sentry/SentyOptionsInternal.m
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
#import "SentryOptions.h"
#import "SentryOptionsInternal.h"
#import "SentrySessionReplayIntegration.h"
#import "SentryStdoutLogIntegration.h"
#import "SentrySwift.h"
#import "SentrySwiftAsyncIntegration.h"

Expand Down Expand Up @@ -54,7 +55,7 @@ @implementation SentryOptionsInternal
[SentryANRTrackingIntegration class], [SentryAutoBreadcrumbTrackingIntegration class],
[SentryAutoSessionTrackingIntegration class], [SentryCoreDataTrackingIntegration class],
[SentryFileIOTrackingIntegration class], [SentryNetworkTrackingIntegration class],
[SentrySwiftAsyncIntegration class], nil];
[SentryStdOutLogIntegration class], [SentrySwiftAsyncIntegration class], nil];

#if TARGET_OS_IOS && SENTRY_HAS_UIKIT
[defaultIntegrations addObject:[SentryUserFeedbackIntegration class]];
Expand Down
21 changes: 21 additions & 0 deletions Sources/Sentry/include/SentryStdOutLogIntegration.h
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
4 changes: 0 additions & 4 deletions Sources/Swift/Tools/SentryLogger.swift
Original file line number Diff line number Diff line change
Expand Up @@ -216,10 +216,6 @@ public final class SentryLogger: NSObject {
}

if let processedLog {
SentrySDKLog.log(
message: "[SentryLogger] \(processedLog.body)",
andLevel: processedLog.level.toSentryLevel()
)
batcher.add(processedLog)
}
}
Expand Down
Loading
Loading