Skip to content

Commit 2108fe1

Browse files
committed
Log forwarder
1 parent 87a35b9 commit 2108fe1

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
#import <Foundation/Foundation.h>
2+
#import <React/RCTEventEmitter.h>
3+
4+
NS_ASSUME_NONNULL_BEGIN
5+
6+
/**
7+
* Singleton class that forwards native Sentry SDK logs to JavaScript via React Native events.
8+
* This allows React Native developers to see native SDK logs in the Metro console.
9+
*/
10+
@interface RNSentryNativeLogsForwarder : NSObject
11+
12+
/**
13+
* Returns the shared instance of the logs forwarder.
14+
*/
15+
+ (instancetype)shared;
16+
17+
/**
18+
* Configures the forwarder with the event emitter to use for sending events to JS.
19+
* Call this when the React Native module starts observing events.
20+
*
21+
* @param emitter The RCTEventEmitter instance (typically the RNSentry module).
22+
*/
23+
- (void)configureWithEventEmitter:(RCTEventEmitter *)emitter;
24+
25+
/**
26+
* Clears the event emitter reference.
27+
* Call this when the React Native module stops observing events.
28+
*/
29+
- (void)stopForwarding;
30+
31+
@end
32+
33+
NS_ASSUME_NONNULL_END
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#import "RNSentryNativeLogsForwarder.h"
2+
3+
@import Sentry;
4+
5+
static NSString *const RNSentryNativeLogEventName = @"SentryNativeLog";
6+
7+
@interface RNSentryNativeLogsForwarder ()
8+
9+
@property (nonatomic, weak) RCTEventEmitter *eventEmitter;
10+
11+
@end
12+
13+
@implementation RNSentryNativeLogsForwarder
14+
15+
+ (instancetype)shared
16+
{
17+
static RNSentryNativeLogsForwarder *instance = nil;
18+
static dispatch_once_t onceToken;
19+
dispatch_once(&onceToken, ^{ instance = [[RNSentryNativeLogsForwarder alloc] init]; });
20+
return instance;
21+
}
22+
23+
- (void)configureWithEventEmitter:(RCTEventEmitter *)emitter
24+
{
25+
self.eventEmitter = emitter;
26+
27+
__weak RNSentryNativeLogsForwarder *weakSelf = self;
28+
29+
// Set up the Sentry SDK log output to forward logs to JS
30+
[SentrySDKLog setOutput:^(NSString *_Nonnull message) {
31+
// Always print to console (default behavior)
32+
NSLog(@"%@", message);
33+
34+
// Forward to JS if we have an emitter
35+
RNSentryNativeLogsForwarder *strongSelf = weakSelf;
36+
if (strongSelf) {
37+
[strongSelf forwardLogMessage:message];
38+
}
39+
}];
40+
}
41+
42+
- (void)stopForwarding
43+
{
44+
self.eventEmitter = nil;
45+
46+
// Reset to default print behavior
47+
[SentrySDKLog setOutput:^(NSString *_Nonnull message) { NSLog(@"%@", message); }];
48+
}
49+
50+
- (void)forwardLogMessage:(NSString *)message
51+
{
52+
RCTEventEmitter *emitter = self.eventEmitter;
53+
if (emitter == nil) {
54+
return;
55+
}
56+
57+
// Parse the log message to extract level and component
58+
// Format: "[Sentry] [level] [timestamp] [Component:line] message"
59+
// or: "[Sentry] [level] [timestamp] message"
60+
NSString *level = [self extractLevelFromMessage:message];
61+
NSString *component = [self extractComponentFromMessage:message];
62+
NSString *cleanMessage = [self extractCleanMessageFromMessage:message];
63+
64+
NSDictionary *body = @{
65+
@"level" : level,
66+
@"component" : component,
67+
@"message" : cleanMessage,
68+
};
69+
70+
[emitter sendEventWithName:RNSentryNativeLogEventName body:body];
71+
}
72+
73+
- (NSString *)extractLevelFromMessage:(NSString *)message
74+
{
75+
// Look for patterns like [debug], [info], [warning], [error], [fatal]
76+
NSRegularExpression *regex =
77+
[NSRegularExpression regularExpressionWithPattern:@"\\[(debug|info|warning|error|fatal)\\]"
78+
options:NSRegularExpressionCaseInsensitive
79+
error:nil];
80+
81+
NSTextCheckingResult *match = [regex firstMatchInString:message
82+
options:0
83+
range:NSMakeRange(0, message.length)];
84+
85+
if (match && match.numberOfRanges > 1) {
86+
return [[message substringWithRange:[match rangeAtIndex:1]] lowercaseString];
87+
}
88+
89+
return @"info";
90+
}
91+
92+
- (NSString *)extractComponentFromMessage:(NSString *)message
93+
{
94+
// Look for pattern like [ComponentName:123]
95+
NSRegularExpression *regex =
96+
[NSRegularExpression regularExpressionWithPattern:@"\\[([A-Za-z]+):\\d+\\]"
97+
options:0
98+
error:nil];
99+
100+
NSTextCheckingResult *match = [regex firstMatchInString:message
101+
options:0
102+
range:NSMakeRange(0, message.length)];
103+
104+
if (match && match.numberOfRanges > 1) {
105+
return [message substringWithRange:[match rangeAtIndex:1]];
106+
}
107+
108+
return @"Sentry";
109+
}
110+
111+
- (NSString *)extractCleanMessageFromMessage:(NSString *)message
112+
{
113+
// Remove the prefix parts: [Sentry] [level] [timestamp] [Component:line]
114+
// and return just the actual message content
115+
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:
116+
@"^\\[Sentry\\]\\s*\\[[^\\]]+\\]\\s*\\[[^\\]]+\\]\\s*(?:\\[[^\\]]+\\]\\s*)?"
117+
options:0
118+
error:nil];
119+
120+
NSString *cleanMessage = [regex stringByReplacingMatchesInString:message
121+
options:0
122+
range:NSMakeRange(0, message.length)
123+
withTemplate:@""];
124+
125+
return [cleanMessage stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
126+
}
127+
128+
@end

0 commit comments

Comments
 (0)