11import 'dart:async' ;
2+ import 'dart:mirrors' ;
3+
4+ import 'package:pubnub/pubnub.dart' ;
5+ import '../../networking/response/response.dart' ;
6+ import '../net/request_type.dart' ;
7+ import '../core.dart' ;
28
39final _pubnubLoggerModuleKey = #pubnub.logging;
410
@@ -46,6 +52,9 @@ class LazyLogger implements ILogger {
4652 @override
4753 void shout (message) => logger.shout (message);
4854
55+ @override
56+ void fine (message) => logger.fine (message);
57+
4958 @override
5059 void silly (message) => logger.silly (message);
5160
@@ -62,10 +71,41 @@ class LazyLogger implements ILogger {
6271///
6372/// If there is no provider, returned logger will be a [DummyLogger] .
6473LazyLogger injectLogger (String id) {
65- return LazyLogger (
66- id, () => Zone .current[_pubnubLoggerModuleKey] ?? DummyLogger ());
74+ return LazyLogger (id, () {
75+ // First check the current Zone for a logger (existing behavior)
76+ var zoneLogger = Zone .current[_pubnubLoggerModuleKey];
77+ if (zoneLogger != null ) {
78+ return zoneLogger;
79+ }
80+
81+ // If no Zone logger, check for global PubNub logger (NEW)
82+ var globalLogger = _getGlobalPubNubLogger ();
83+ if (globalLogger != null ) {
84+ return globalLogger;
85+ }
86+
87+ // Fallback to dummy logger
88+ return DummyLogger ();
89+ });
90+ }
91+
92+ /// Get the global PubNub logger if available
93+ ILogger ? _getGlobalPubNubLogger () {
94+ try {
95+ for (var entry in globalLoggerRegistry.entries) {
96+ if (entry.key.startsWith ('pubnub-' )) {
97+ return entry.value;
98+ }
99+ }
100+ return null ;
101+ } catch (e) {
102+ return null ;
103+ }
67104}
68105
106+ /// Global registry for loggers that can be accessed across the SDK
107+ final Map <String , ILogger > globalLoggerRegistry = {};
108+
69109abstract class ILogger {
70110 ILogger get (String id);
71111 void log (int level, dynamic message);
@@ -75,6 +115,7 @@ abstract class ILogger {
75115 void severe (dynamic message) => log (Level .severe, message);
76116 void warning (dynamic message) => log (Level .warning, message);
77117 void info (dynamic message) => log (Level .info, message);
118+ void fine (dynamic message) => log (Level .fine, message);
78119 void verbose (dynamic message) => log (Level .verbose, message);
79120 void silly (dynamic message) => log (Level .silly, message);
80121}
@@ -102,6 +143,9 @@ abstract class Level {
102143 /// Intended for informational messages.
103144 static final int info = 160 ;
104145
146+ /// Intended for the fine mode.
147+ static final int fine = 500 ;
148+
105149 /// Intended for the verbose mode.
106150 static final int verbose = 320 ;
107151
@@ -119,6 +163,7 @@ abstract class Level {
119163 80 : 'warning' ,
120164 160 : 'info' ,
121165 320 : 'verbose' ,
166+ 500 : 'fine' ,
122167 640 : 'silly' ,
123168 10000 : 'all'
124169 };
@@ -130,3 +175,189 @@ abstract class Level {
130175 .value;
131176 }
132177}
178+
179+ Map <String , dynamic > extractObjectProperties (Object obj,
180+ {List <String > skipProperties = const ['keyset' ]}) {
181+ final result = < String , dynamic > {};
182+
183+ try {
184+ final mirror = reflect (obj);
185+ final declarations = mirror.type.declarations;
186+
187+ for (var declaration in declarations.values) {
188+ if (declaration is VariableMirror && ! declaration.isStatic) {
189+ final name = MirrorSystem .getName (declaration.simpleName);
190+
191+ if (skipProperties.contains (name)) {
192+ continue ;
193+ }
194+
195+ try {
196+ final value = mirror.getField (declaration.simpleName).reflectee;
197+ result[name] = value;
198+ } catch (e) {
199+ result[name] = '<inaccessible>' ;
200+ }
201+ }
202+ }
203+ } catch (e) {
204+ // Fallback: try to convert to string representation
205+ result['toString' ] = obj.toString ();
206+ }
207+
208+ return result;
209+ }
210+
211+ /// Enum for different types of log event details
212+ enum LogEventDetailsType {
213+ pubNubInstanceInfo,
214+ apiParametersInfo,
215+ networkRequestInfo,
216+ networkResponseInfo,
217+ }
218+
219+ /// Enhanced LogEvent that can handle both Map and Object details
220+ class LogEvent {
221+ final String ? message;
222+ final Object ? details;
223+ final LogEventDetailsType ? detailsType;
224+
225+ LogEvent ({this .message, this .details, this .detailsType});
226+
227+ @override
228+ String toString () {
229+ var messageString = message ?? '' ;
230+ try {
231+ if (detailsType == LogEventDetailsType .pubNubInstanceInfo) {
232+ var pubnub = details as PubNub ;
233+ var keysets = pubnub.keysets;
234+
235+ messageString += '\n\t PubNub Instance Information:' ;
236+ messageString += '\n\t Instance ID: ${Core .instanceId }' ;
237+ messageString += '\n\t Version: ${Core .version }' ;
238+
239+ // Log module information
240+ messageString += '\n\t Modules:' ;
241+ messageString +=
242+ '\n\t Networking: ${pubnub .networking .runtimeType .toString ().split ('.' ).last }' ;
243+ messageString +=
244+ '\n\t Parser: ${pubnub .parser .runtimeType .toString ().split ('.' ).last }' ;
245+ messageString +=
246+ '\n\t Crypto: ${pubnub .crypto .runtimeType .toString ().split ('.' ).last }' ;
247+
248+ // Log keyset information
249+ messageString += '\n\t Keysets:' ;
250+ var allKeysets = keysets.keysets;
251+ if (allKeysets.isEmpty) {
252+ messageString += '\n\t No keysets configured' ;
253+ } else {
254+ // Get all keyset names by trying to access them
255+ var keysetNames = < String > [];
256+ try {
257+ // Try to get the default keyset name by checking which one is the default
258+ var defaultKeyset = keysets.defaultKeyset;
259+ for (var keyset in allKeysets) {
260+ // Find the name by comparing with default
261+ if (keyset == defaultKeyset) {
262+ keysetNames.add ('default' );
263+ } else {
264+ // For non-default keysets, we'll use a generic name
265+ keysetNames.add ('keyset_${keysetNames .length + 1 }' );
266+ }
267+ }
268+ } catch (e) {
269+ // If no default keyset, just number them
270+ for (var i = 0 ; i < allKeysets.length; i++ ) {
271+ keysetNames.add ('keyset_${i + 1 }' );
272+ }
273+ }
274+
275+ for (var i = 0 ; i < allKeysets.length; i++ ) {
276+ var keyset = allKeysets[i];
277+ var name = keysetNames[i];
278+ messageString += '\n\t $name :' ;
279+ messageString += '\n\t Subscribe Key: ${keyset .subscribeKey }' ;
280+ messageString +=
281+ '\n\t Publish Key: ${keyset .publishKey ?? 'not provided' }' ;
282+ messageString +=
283+ '\n\t Secret Key: ${keyset .secretKey != null ? 'provided' : 'not provided' }' ;
284+ messageString += '\n\t User ID: ${keyset .userId }' ;
285+ messageString +=
286+ '\n\t Auth Key: ${keyset .authKey != null ? 'provided' : 'not provided' }' ;
287+ messageString +=
288+ '\n\t Cipher Key: ${keyset .cipherKey != null ? 'provided' : 'not provided' }' ;
289+ if (keyset.settings.isNotEmpty) {
290+ messageString += '\n\t Settings: ${keyset .settings }' ;
291+ }
292+ if (i == 0 && keysetNames[i] == 'default' ) {
293+ messageString += '\n\t (Default)' ;
294+ }
295+ }
296+ }
297+ } else if (detailsType == LogEventDetailsType .apiParametersInfo) {
298+ // Handle both Map and Object types
299+ Map <String , dynamic > detailsMap;
300+ if (details is Map ) {
301+ detailsMap = details as Map <String , dynamic >;
302+ } else if (details != null ) {
303+ // Extract properties from any object
304+ detailsMap = extractObjectProperties (details! );
305+ } else {
306+ detailsMap = {};
307+ }
308+ messageString +=
309+ '\n\t ${detailsMap .entries .map ((entry ) => '${entry .key }: ${entry .value }' ).join ('\n\t ' )}' ;
310+ } else if (detailsType == LogEventDetailsType .networkRequestInfo) {
311+ var requestMap = details as Map <String , dynamic >;
312+
313+ for (var entry in requestMap.entries) {
314+ switch (entry.key) {
315+ case 'type' :
316+ var requestType = entry.value as RequestType ;
317+ messageString += '\n\t Method: ${requestType .method }' ;
318+ break ;
319+ case 'uri' :
320+ messageString += '\n\t URL: ${entry .value }' ;
321+ break ;
322+ case 'headers' :
323+ var headers = entry.value as Map <String , dynamic >;
324+ if (headers.isNotEmpty) {
325+ messageString += '\n\t Headers:' ;
326+ for (var headerEntry in headers.entries) {
327+ messageString +=
328+ '\n\t ${headerEntry .key }: ${headerEntry .value }' ;
329+ }
330+ }
331+ break ;
332+ case 'body' :
333+ if (entry.value != null ) {
334+ if (entry.value is List <int >) {
335+ var length = entry.value.length;
336+ messageString +=
337+ '\n\t Body:binary content with length $length ' ;
338+ } else {
339+ messageString += '\n\t Body: ${entry .value }' ;
340+ }
341+ }
342+ break ;
343+ }
344+ }
345+ } else if (detailsType == LogEventDetailsType .networkResponseInfo) {
346+ var responseMap = details as Map <String , dynamic >;
347+ messageString += '\n\t URL: ${responseMap ['request' ].uri }' ;
348+ messageString +=
349+ '\n\t Status Code: ${responseMap ['response' ].statusCode }' ;
350+ var response = responseMap['response' ] as Response ;
351+ if (response.headers.containsKey ('server' )) {
352+ messageString +=
353+ '\n\t Body: binary content length ${response .byteList .length }' ;
354+ } else {
355+ messageString += '\n\t Body: ${response .text }' ;
356+ }
357+ }
358+ } catch (e) {
359+ messageString += '\n logging error: $e ' ;
360+ }
361+ return messageString;
362+ }
363+ }
0 commit comments