Skip to content

Commit d1ecabd

Browse files
authored
feat: Add logging (#1042)
* feat: add logging to gotrue * feat: add logging to postgrest * feat: add logging to supabase package * fix: log sensitive data at level finer * feat: add logging to supabase_flutter * feat: add logging to functions_client * feat: add logging to realtime_client * refactor: log sensitive data with level finest * feat: add logging to storage_client * docs: add documentation to supabase_flutter readme * fix: log headers with level FINEST * fix: save log stream subscription and print all from all loggers * fix: log some realtime records with level FINE
1 parent 1a3c3f7 commit d1ecabd

24 files changed

+244
-65
lines changed

packages/functions_client/lib/src/functions_client.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import 'dart:typed_data';
33

44
import 'package:functions_client/src/constants.dart';
55
import 'package:functions_client/src/types.dart';
6+
import 'package:functions_client/src/version.dart';
67
import 'package:http/http.dart' as http;
78
import 'package:http/http.dart' show MultipartRequest;
9+
import 'package:logging/logging.dart';
810
import 'package:yet_another_json_isolate/yet_another_json_isolate.dart';
911

1012
class FunctionsClient {
@@ -13,6 +15,7 @@ class FunctionsClient {
1315
final http.Client? _httpClient;
1416
final YAJsonIsolate _isolate;
1517
final bool _hasCustomIsolate;
18+
final _log = Logger("supabase.functions");
1619

1720
/// In case you don't provide your own isolate, call [dispose] when you're done
1821
FunctionsClient(
@@ -24,7 +27,10 @@ class FunctionsClient {
2427
_headers = {...Constants.defaultHeaders, ...headers},
2528
_isolate = isolate ?? (YAJsonIsolate()..initialize()),
2629
_hasCustomIsolate = isolate != null,
27-
_httpClient = httpClient;
30+
_httpClient = httpClient {
31+
_log.config("Initialize FunctionsClient v$version with url: $url");
32+
_log.finest("Initialize with headers: $headers");
33+
}
2834

2935
/// Getter for the headers
3036
Map<String, String> get headers {
@@ -129,6 +135,8 @@ class FunctionsClient {
129135
request.headers[key] = value;
130136
});
131137

138+
_log.finest('Request: ${request.method} ${request.url} ${request.headers}');
139+
132140
final response = await (_httpClient?.send(request) ?? request.send());
133141
final responseType = (response.headers['Content-Type'] ??
134142
response.headers['content-type'] ??
@@ -167,6 +175,7 @@ class FunctionsClient {
167175
///
168176
/// Does nothing if you pass your own isolate
169177
Future<void> dispose() async {
178+
_log.fine("Dispose FunctionsClient");
170179
if (!_hasCustomIsolate) {
171180
return _isolate.dispose();
172181
}

packages/functions_client/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ environment:
1010

1111
dependencies:
1212
http: '>=0.13.4 <2.0.0'
13+
logging: ^1.2.0
1314
yet_another_json_isolate: 2.0.3
1415

1516
dev_dependencies:

packages/gotrue/lib/src/broadcast_web.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@ import 'dart:html' as html;
33
import 'dart:js_util' as js_util;
44

55
import 'package:gotrue/src/types/types.dart';
6+
import 'package:logging/logging.dart';
7+
8+
final _log = Logger('supabase.gotrue');
69

710
BroadcastChannel getBroadcastChannel(String broadcastKey) {
811
final broadcast = html.BroadcastChannel(broadcastKey);
@@ -15,6 +18,8 @@ BroadcastChannel getBroadcastChannel(String broadcastKey) {
1518
return json.decode(json.encode(dataMap));
1619
}),
1720
postMessage: (message) {
21+
_log.finest('Broadcasting message: $message');
22+
_log.fine('Broadcasting event: ${message['event']}');
1823
final jsMessage = js_util.jsify(message);
1924
broadcast.postMessage(jsMessage);
2025
},

packages/gotrue/lib/src/gotrue_client.dart

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ import 'package:jwt_decode/jwt_decode.dart';
1414
import 'package:meta/meta.dart';
1515
import 'package:retry/retry.dart';
1616
import 'package:rxdart/subjects.dart';
17+
import 'package:logging/logging.dart';
1718

1819
import 'broadcast_stub.dart' if (dart.library.html) './broadcast_web.dart'
1920
as web;
21+
import 'version.dart';
2022

2123
part 'gotrue_mfa_api.dart';
2224

@@ -87,6 +89,8 @@ class GoTrueClient {
8789

8890
final AuthFlowType _flowType;
8991

92+
final _log = Logger('supabase.gotrue');
93+
9094
/// Proxy to the web BroadcastChannel API. Should be null on non-web platforms.
9195
BroadcastChannel? _broadcastChannel;
9296

@@ -101,20 +105,22 @@ class GoTrueClient {
101105
GotrueAsyncStorage? asyncStorage,
102106
AuthFlowType flowType = AuthFlowType.pkce,
103107
}) : _url = url ?? Constants.defaultGotrueUrl,
104-
_headers = headers ?? {},
108+
_headers = {
109+
...Constants.defaultHeaders,
110+
...?headers,
111+
},
105112
_httpClient = httpClient,
106113
_asyncStorage = asyncStorage,
107114
_flowType = flowType {
108115
_autoRefreshToken = autoRefreshToken ?? true;
109116

110117
final gotrueUrl = url ?? Constants.defaultGotrueUrl;
111-
final gotrueHeader = {
112-
...Constants.defaultHeaders,
113-
if (headers != null) ...headers,
114-
};
118+
_log.config(
119+
'Initialize GoTrueClient v$version with url: $_url, autoRefreshToken: $_autoRefreshToken, flowType: $_flowType, tickDuration: ${Constants.autoRefreshTickDuration}, tickThreshold: ${Constants.autoRefreshTickThreshold}');
120+
_log.finest('Initialize with headers: $_headers');
115121
admin = GoTrueAdminApi(
116122
gotrueUrl,
117-
headers: gotrueHeader,
123+
headers: _headers,
118124
httpClient: httpClient,
119125
);
120126
mfa = GoTrueMFAApi(
@@ -617,8 +623,10 @@ class GoTrueClient {
617623
/// If the current session's refresh token is invalid, an error will be thrown.
618624
Future<AuthResponse> refreshSession([String? refreshToken]) async {
619625
if (currentSession?.accessToken == null) {
626+
_log.warning("Can't refresh session, no current session found.");
620627
throw AuthSessionMissingException();
621628
}
629+
_log.info('Refresh session');
622630

623631
final currentSessionRefreshToken =
624632
refreshToken ?? _currentSession?.refreshToken;
@@ -842,6 +850,7 @@ class GoTrueClient {
842850
Future<void> signOut({
843851
SignOutScope scope = SignOutScope.local,
844852
}) async {
853+
_log.info('Signing out user with scope: $scope');
845854
final accessToken = currentSession?.accessToken;
846855

847856
if (scope != SignOutScope.others) {
@@ -966,13 +975,15 @@ class GoTrueClient {
966975
try {
967976
final session = Session.fromJson(json.decode(jsonStr));
968977
if (session == null) {
978+
_log.warning("Can't recover session from string, session is null");
969979
await signOut();
970980
throw notifyException(
971981
AuthException('Current session is missing data.'),
972982
);
973983
}
974984

975985
if (session.isExpired) {
986+
_log.fine('Session from recovery is expired');
976987
final refreshToken = session.refreshToken;
977988
if (_autoRefreshToken && refreshToken != null) {
978989
return await _callRefreshToken(refreshToken);
@@ -1002,6 +1013,7 @@ class GoTrueClient {
10021013
void startAutoRefresh() async {
10031014
stopAutoRefresh();
10041015

1016+
_log.fine('Starting auto refresh');
10051017
_autoRefreshTicker = Timer.periodic(
10061018
Constants.autoRefreshTickDuration,
10071019
(Timer t) => _autoRefreshTokenTick(),
@@ -1013,6 +1025,7 @@ class GoTrueClient {
10131025

10141026
/// Stops an active auto refresh process running in the background (if any).
10151027
void stopAutoRefresh() {
1028+
_log.fine('Stopping auto refresh');
10161029
_autoRefreshTicker?.cancel();
10171030
_autoRefreshTicker = null;
10181031
}
@@ -1037,12 +1050,15 @@ class GoTrueClient {
10371050
Constants.autoRefreshTickDuration.inMilliseconds)
10381051
.floor();
10391052

1053+
_log.finer('Access token expires in $expiresInTicks ticks');
1054+
10401055
// Only tick if the next tick comes after the retry threshold
10411056
if (expiresInTicks <= Constants.autoRefreshTickThreshold) {
10421057
await _callRefreshToken(refreshToken);
10431058
}
10441059
} catch (error) {
1045-
// Do nothing. JS client prints here
1060+
// Do nothing. JS client prints here, but error is already tracked via
1061+
// [notifyException]
10461062
}
10471063
}
10481064

@@ -1055,6 +1071,7 @@ class GoTrueClient {
10551071
// Make a GET request
10561072
() async {
10571073
attempt++;
1074+
_log.fine('Attempt $attempt to refresh token');
10581075
final options = GotrueRequestOptions(
10591076
headers: _headers,
10601077
body: {'refresh_token': refreshToken},
@@ -1129,11 +1146,14 @@ class GoTrueClient {
11291146

11301147
/// set currentSession and currentUser
11311148
void _saveSession(Session session) {
1149+
_log.finest('Saving session: $session');
1150+
_log.fine('Saving session');
11321151
_currentSession = session;
11331152
_currentUser = session.user;
11341153
}
11351154

11361155
void _removeSession() {
1156+
_log.fine('Removing session');
11371157
_currentSession = null;
11381158
_currentUser = null;
11391159
}
@@ -1151,6 +1171,8 @@ class GoTrueClient {
11511171
_broadcastChannelSubscription =
11521172
_broadcastChannel?.onMessage.listen((messageEvent) {
11531173
final rawEvent = messageEvent['event'];
1174+
_log.finest('Received broadcast message: $messageEvent');
1175+
_log.info('Received broadcast event: $rawEvent');
11541176
final event = switch (rawEvent) {
11551177
// This library sends the js name of the event to be comptabile with
11561178
// the js library, so we need to convert it back to the dart name
@@ -1202,6 +1224,7 @@ class GoTrueClient {
12021224
Future<AuthResponse> _callRefreshToken(String refreshToken) async {
12031225
// Refreshing is already in progress
12041226
if (_refreshTokenCompleter != null) {
1227+
_log.finer("Don't call refresh token, already in progress");
12051228
return _refreshTokenCompleter!.future;
12061229
}
12071230

@@ -1213,6 +1236,7 @@ class GoTrueClient {
12131236
(_) => null,
12141237
onError: (_, __) => null,
12151238
);
1239+
_log.fine('Refresh access token');
12161240

12171241
final data = await _refreshAccessToken(refreshToken);
12181242

@@ -1232,15 +1256,15 @@ class GoTrueClient {
12321256
_removeSession();
12331257
notifyAllSubscribers(AuthChangeEvent.signedOut);
12341258
} else {
1235-
_onAuthStateChangeController.addError(error, stack);
1259+
notifyException(error, stack);
12361260
}
12371261

12381262
_refreshTokenCompleter?.completeError(error);
12391263

12401264
rethrow;
12411265
} catch (error, stack) {
12421266
_refreshTokenCompleter?.completeError(error);
1243-
_onAuthStateChangeController.addError(error, stack);
1267+
notifyException(error, stack);
12441268
rethrow;
12451269
} finally {
12461270
_refreshTokenCompleter = null;
@@ -1265,13 +1289,15 @@ class GoTrueClient {
12651289
});
12661290
}
12671291
final state = AuthState(event, session, fromBroadcast: !broadcast);
1292+
_log.finest('onAuthStateChange: $state');
12681293
_onAuthStateChangeController.add(state);
12691294
_onAuthStateChangeControllerSync.add(state);
12701295
}
12711296

12721297
/// For internal use only.
12731298
@internal
12741299
Object notifyException(Object exception, [StackTrace? stackTrace]) {
1300+
_log.warning('Notifying exception', exception, stackTrace);
12751301
_onAuthStateChangeController.addError(
12761302
exception,
12771303
stackTrace ?? StackTrace.current,

packages/gotrue/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ dependencies:
1616
retry: ^3.1.0
1717
rxdart: '>=0.27.7 <0.29.0'
1818
meta: ^1.7.0
19+
logging: ^1.2.0
1920

2021
dev_dependencies:
2122
dart_jsonwebtoken: ^2.4.1

packages/postgrest/lib/src/postgrest.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:http/http.dart';
2+
import 'package:logging/logging.dart';
23
import 'package:postgrest/postgrest.dart';
34
import 'package:postgrest/src/constants.dart';
45
import 'package:yet_another_json_isolate/yet_another_json_isolate.dart';
@@ -11,6 +12,7 @@ class PostgrestClient {
1112
final Client? httpClient;
1213
final YAJsonIsolate _isolate;
1314
final bool _hasCustomIsolate;
15+
final _log = Logger('supabase.postgrest');
1416

1517
/// To create a [PostgrestClient], you need to provide an [url] endpoint.
1618
///
@@ -32,7 +34,10 @@ class PostgrestClient {
3234
}) : _schema = schema,
3335
headers = {...defaultHeaders, if (headers != null) ...headers},
3436
_isolate = isolate ?? (YAJsonIsolate()..initialize()),
35-
_hasCustomIsolate = isolate != null;
37+
_hasCustomIsolate = isolate != null {
38+
_log.config('Initialize PostgrestClient with url: $url, schema: $_schema');
39+
_log.finest('Initialize with headers: $headers');
40+
}
3641

3742
/// Authenticates the request with JWT.
3843
@Deprecated("Use setAuth() instead")
@@ -42,6 +47,7 @@ class PostgrestClient {
4247
}
4348

4449
PostgrestClient setAuth(String? token) {
50+
_log.finest("setAuth with: $token");
4551
if (token != null) {
4652
headers['Authorization'] = 'Bearer $token';
4753
} else {
@@ -95,6 +101,7 @@ class PostgrestClient {
95101
}
96102

97103
Future<void> dispose() async {
104+
_log.fine("dispose PostgrestClient");
98105
if (!_hasCustomIsolate) {
99106
return _isolate.dispose();
100107
}

packages/postgrest/lib/src/postgrest_builder.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import 'dart:core';
66

77
import 'package:http/http.dart' as http;
88
import 'package:http/http.dart';
9+
import 'package:logging/logging.dart';
910
import 'package:meta/meta.dart';
1011
import 'package:postgrest/postgrest.dart';
1112
import 'package:yet_another_json_isolate/yet_another_json_isolate.dart';
@@ -44,6 +45,7 @@ class PostgrestBuilder<T, S, R> implements Future<T> {
4445
final Client? _httpClient;
4546
final YAJsonIsolate? _isolate;
4647
final CountOption? _count;
48+
final _log = Logger('supabase.postgrest');
4749

4850
PostgrestBuilder({
4951
required Uri url,
@@ -132,6 +134,8 @@ class PostgrestBuilder<T, S, R> implements Future<T> {
132134
_headers['Content-Type'] = 'application/json';
133135
}
134136
final bodyStr = jsonEncode(_body);
137+
_log.finest("Request: $uppercaseMethod $_url");
138+
135139
if (uppercaseMethod == METHOD_GET) {
136140
response = await (_httpClient?.get ?? http.get)(
137141
_url,
@@ -203,14 +207,17 @@ class PostgrestBuilder<T, S, R> implements Future<T> {
203207
// Workaround for https://github.com/supabase/supabase-flutter/issues/560
204208
if (_maybeSingle && method.toUpperCase() == 'GET' && body is List) {
205209
if (body.length > 1) {
206-
throw PostgrestException(
210+
final exception = PostgrestException(
207211
// https://github.com/PostgREST/postgrest/blob/a867d79c42419af16c18c3fb019eba8df992626f/src/PostgREST/Error.hs#L553
208212
code: '406',
209213
details:
210214
'Results contain ${body.length} rows, application/vnd.pgrst.object+json requires 1 row',
211215
hint: null,
212216
message: 'JSON object requested, multiple (or no) rows returned',
213217
);
218+
219+
_log.finest('$exception for request $_url');
220+
throw exception;
214221
} else if (body.length == 1) {
215222
body = body.first;
216223
} else {
@@ -286,6 +293,9 @@ class PostgrestBuilder<T, S, R> implements Future<T> {
286293
);
287294
}
288295

296+
_log.finest('$error from request: $_url');
297+
_log.fine('$error from request');
298+
289299
throw error;
290300
}
291301
}

packages/postgrest/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ dependencies:
1212
http: '>=0.13.0 <2.0.0'
1313
yet_another_json_isolate: 2.0.3
1414
meta: ^1.9.1
15+
logging: ^1.2.0
1516

1617
dev_dependencies:
1718
collection: ^1.16.0

0 commit comments

Comments
 (0)