Snap Logger : le guide du logging moderne pour apps mobile
Le logging mobile a changé. Fini les NSLog et Logcat bruts : en 2026, un bon logger mobile gère des niveaux, structure ses messages, redacte les données sensibles, sample en production et pousse les logs vers un backend centralisé. Ce guide détaillé compare les systèmes de logging natifs (NSLog, os_log, Logcat, Timber, flogger), explique le remote logging, et montre comment intégrer l'API SnapLogs dans Flutter, React Native, iOS et Android avec des exemples de code concrets.
SnapLogs Team
Équipe produit

Snap Logger : le guide du logging moderne pour apps mobile
Le logging est le plus vieux réflexe du développeur, et pourtant il reste mal fait dans 90% des apps mobile. NSLog verbeux, Logcat sans structure, logs qui disparaissent après redémarrage, données sensibles en clair : les anti-patterns sont légion. Ce guide vous donne le socle moderne du logging mobile en 2026, compare les systèmes natifs, et montre comment intégrer SnapLogs comme couche de remote logging universelle.
Pourquoi le logging mobile moderne est critique
Le logging mobile sert trois objectifs distincts :
- Debug local : comprendre ce qui se passe pendant le dev.
- Debug production : diagnostiquer les bugs remontés par les utilisateurs.
- Observabilité produit : mesurer les flux, les conversions, les churns.
Historiquement, on confondait ces trois objectifs avec un seul outil (NSLog). Aujourd'hui, on les sépare :
- Dev local :
debugPrint(Flutter),os_log(iOS),Log.d(Android). - Production : remote logging via SnapLogs, Sentry, Bugsnag.
- Produit : analytics (Amplitude, Mixpanel).
Cet article se concentre sur le debug production, le plus mal couvert aujourd'hui.
Les composants d'un logger mobile moderne
Un logger mobile moderne doit gérer six dimensions.
1. Les niveaux de log
Cinq niveaux standards :
| Niveau | Usage | En production |
| --- | --- | --- |
| debug | Dev, traces internes | Désactivé |
| verbose | Détails très fins | Désactivé |
| info | Événements métier | Activé |
| warning | Anomalies récupérables | Activé |
| error | Exceptions catchées | Activé |
| fatal | Crashes imminents | Activé |
L'erreur classique est de tout logger en info. Résultat : le bruit masque le signal. SnapLogs recommande :
info: événements métier (login, payment, navigation).warning: comportement inhabituel mais géré (retry, fallback).error: exception catchée mais inattendue.
2. Le structured logging
Le structured logging remplace le texte libre par des paires clé-valeur.
Texte libre :
User 42 paid 49.99 EUR with card
Structuré :
{
"level": "info",
"message": "payment_done",
"fields": {
"user_id": 42,
"amount": 49.99,
"currency": "EUR",
"method": "card"
},
"timestamp": "2026-08-09T11:03:00Z"
}
Le structuré permet :
- Le filtrage par champ côté backend (tous les paiements > 100€).
- L'agrégation (count par method).
- La corrélation avec d'autres événements (login juste avant).
SnapLogs impose le structuré. Voyez la documentation de l'API.
3. La redaction
Logger du PII (Personally Identifiable Information) est une violation RGPD. Un bon logger redacte par regex :
- Email :
\b[\w.]+@[\w.]+\b - IBAN :
\b[A-Z]{2}\d{2}[A-Z0-9]{12,30}\b - Carte bancaire :
\b\d{13,16}\b - Token JWT :
\beyJ[\w-]+\.[\w-]+\.[\w-]+\b
SnapLogs intègre la redaction nativement :
await SnapLogs.init(
apiKey: const String.fromEnvironment('SNAPLOGS_KEY'),
redactPatterns: [
r'\b[\w.]+@[\w.]+\b',
r'\b[A-Z]{2}\d{2}[A-Z0-9]{12,30}\b',
r'\b\d{13,16}\b',
],
);
Tout log passé par SnapLogs est automatiquement filtré.
4. Le sampling
Logger 100% des events en production tue la batterie et le forfait data. On sample :
error: 100% (jamais samplé).warning: 100% (volume bas).info: 1% à 10% selon le volume.debug: 0% en prod.
await SnapLogs.init(
apiKey: key,
sampleRate: 0.05, // 5% des info logs
errorSampleRate: 1.0, // 100% des errors
);
5. Le batching et l'envoi réseau
Un log par requête HTTP est prohibitif. On batch :
- Buffer local de 50 à 200 logs.
- Flush toutes les 30 secondes ou quand le buffer est plein.
- Retry avec backoff exponentiel (1s, 2s, 4s, 8s).
- File d'attente persistante (SQLite) pour la hors-ligne.
SnapLogs gère tout cela côté SDK. Vous ne voyez que l'API log().
6. La corrélation avec les crashs
Un log sans crash est utile. Un log juste avant un crash est crucial. SnapLogs attache automatiquement les 50 derniers logs à un rapport de crash. Vous obtenez la timeline complète du bug.
Comparaison des log systems natifs
Voyons ce que proposent les natifs, et pourquoi ils ne suffisent plus.
iOS : NSLog vs os_log vs Logger
NSLog est l'ancêtre. Verbeux, non structuré, sync. À éviter.
NSLog("User \(userId) paid \(amount)")
os_log (iOS 10+) est le remplaçant. Structuré, async, performant.
import os
let log = OSLog(subsystem: "pro.snaplogs.demo", category: "payment")
os_log("User %{public}d paid %{public}.2f", log: log, type: .info, userId, amount)
Logger (iOS 14+) est l'API Swift moderne :
import os
let logger = Logger(subsystem: "pro.snaplogs.demo", category: "payment")
logger.info("User \(userId) paid \(amount)")
Limite majeure : os_log et Logger écrivent dans le système, pas dans un backend distant. Pour le debug production, il faut ajouter un remote logger.
Android : Logcat vs Timber vs flogger
Logcat est l'API brute :
import android.util.Log
Log.d("Payment", "User $userId paid $amount")
Verbeux, non structuré, disparaît au redémarrage.
Timber (Jake Wharton) est le standard de fait :
import timber.log.Timber
Timber.plant(Timber.DebugTree())
Timber.i("User %d paid %.2f", userId, amount)
Timber.e(exception, "Payment failed for user %d", userId)
Timber plante des arbres (tree pattern). On peut ajouter un SnapLogsTree :
class SnapLogsTree : Timber.Tree() {
override fun log(priority: Int, tag: String?, message: String, t: Throwable?) {
val level = when (priority) {
Log.DEBUG -> LogLevel.debug
Log.INFO -> LogLevel.info
Log.WARN -> LogLevel.warning
Log.ERROR -> LogLevel.error
else -> LogLevel.info
}
SnapLogs.log(level = level, message = message, throwable = t)
}
}
Timber.plant(SnapLogsTree())
flogger (Google) est une alternative Java-style, moins idiomatique Kotlin.
Flutter : debugPrint vs developer.log
debugPrint est simple :
debugPrint('User $userId paid $amount');
Mais il disparaît en release mode et n'est pas structuré.
dart:developer est plus riche :
import 'dart:developer' as developer;
developer.log(
'payment_done',
name: 'payment',
error: exception,
);
Mais il reste local. Pour la prod, il faut SnapLogs :
SnapLogs.log(
level: LogLevel.info,
message: 'payment_done',
fields: {'user_id': userId, 'amount': amount},
);
React Native : console.log vs react-native-logs
console.log fonctionne en dev mais disparaît en prod bundle.
react-native-logs est une lib communautaire. On peut y brancher SnapLogs :
import logger from 'react-native-logs';
import { SnapLogsTransport } from 'snaplogs-react-native';
const log = logger.createLogger({
transport: SnapLogsTransport,
transportOptions: { apiKey: process.env.SNAPLOGS_KEY },
levels: { debug: 0, info: 1, warn: 2, error: 3 },
});
log.info('payment_done', { user_id: 42, amount: 49.99 });
Remote logging : pourquoi c'est le game changer
Le remote logging résout trois problèmes :
- Disparition au redémarrage : les logs natifs sont perdus. Le remote les persiste.
- Pas d'accès au device : vous ne pouvez pas demander à l'utilisateur de brancher son téléphone.
- Corrélation : un backend centralisé permet de croiser les logs de tous les users.
Sans remote logging, vous debugguez à l'aveugle pour 99% des bugs signalés par les utilisateurs. C'est pour cela que le logging natif seul ne suffit plus.

Le volume de logs varie selon l'heure et le traffic. Le sampling et le batching evitent la surcharge tout en capturant les pics d'activite.
L'API SnapLogs log ingestion
SnapLogs expose une API REST v1 pour l'ingestion de logs. Vous pouvez l'appeler directement ou via le SDK.
Endpoint
POST https://api.snaplogs.pro/v1/logs
Content-Type: application/json
Authorization: Bearer YOUR_API_KEY
Payload
{
"logs": [
{
"level": "info",
"message": "payment_done",
"fields": {
"user_id": 42,
"amount": 49.99,
"currency": "EUR",
"method": "card"
},
"timestamp": "2026-08-09T11:03:00.123Z",
"session_id": "sess_abc123"
}
]
}
Réponse
{
"accepted": 1,
"rejected": 0
}
Les logs sont indexés et recherchables dans le dashboard SnapLogs. Vous pouvez les attacher à un rapport de bug, les exporter vers BigQuery, ou les streamer vers Slack via webhook.
Exemples d'intégration SnapLogs
Flutter
import 'package:flutter/material.dart';
import 'package:snaplogs/snaplogs.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await SnapLogs.init(
apiKey: const String.fromEnvironment('SNAPLOGS_KEY'),
environment: 'production',
sampleRate: 0.05,
errorSampleRate: 1.0,
redactPatterns: [r'\b[\w.]+@[\w.]+\b', r'\b\d{16}\b'],
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () async {
try {
await chargeCustomer(amount: 49.99);
SnapLogs.log(
level: LogLevel.info,
message: 'payment_done',
fields: {'amount': 49.99, 'currency': 'EUR'},
);
} catch (e, st) {
SnapLogs.log(
level: LogLevel.error,
message: 'payment_failed',
fields: {'amount': 49.99},
error: e,
stackTrace: st,
);
}
},
child: const Text('Pay 49.99 EUR'),
),
),
),
);
}
}
Future<void> chargeCustomer({required double amount}) async {
// ...
}
React Native
import { SnapLogs } from 'snaplogs-react-native';
SnapLogs.init({
apiKey: process.env.SNAPLOGS_KEY,
sampleRate: 0.05,
redactPatterns: [/[\w.]+@[\w.]+/, /\b\d{16}\b/],
});
// Usage
async function pay(amount) {
try {
await api.post('/charge', { amount });
SnapLogs.log({
level: 'info',
message: 'payment_done',
fields: { amount, currency: 'EUR' },
});
} catch (error) {
SnapLogs.log({
level: 'error',
message: 'payment_failed',
fields: { amount },
error,
});
}
}
iOS (Swift)
import SnapLogs
@main
struct MyApp: App {
init() {
SnapLogs.configure(
apiKey: "YOUR_KEY",
sampleRate: 0.05,
redactPatterns: [#"[\w.]+@[\w.]+"#, #"\b\d{16}\b"#]
)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
// Usage
func pay(amount: Double) {
Task {
do {
try await api.charge(amount: amount)
SnapLogs.log(level: .info, message: "payment_done", fields: ["amount": amount])
} catch {
SnapLogs.log(level: .error, message: "payment_failed", fields: ["amount": amount], error: error)
}
}
}
Android (Kotlin)
import pro.snaplogs.sdk.SnapLogs
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
SnapLogs.init(
apiKey = "YOUR_KEY",
sampleRate = 0.05,
redactPatterns = listOf(Regex("[\\w.]+@[\\w.]+"), Regex("\\b\\d{16}\\b"))
)
}
}
// Usage
suspend fun pay(amount: Double) {
try {
api.charge(amount)
SnapLogs.log(level = LogLevel.INFO, message = "payment_done", fields = mapOf("amount" to amount))
} catch (e: Exception) {
SnapLogs.log(level = LogLevel.ERROR, message = "payment_failed", fields = mapOf("amount" to amount), error = e)
}
}
Best practices du logging mobile
1. Toujours logger avec un niveau
N'utilisez jamais log(message) sans niveau. Précisez info, warning, error. Cela permet le filtrage côté backend.
2. Structurer les messages
Préférez payment_done avec fields {amount: 49.99} à User 42 paid 49.99 EUR with card. Le premier est filtrable, le second est du bruit.
3. Ne jamais logger de PII
Même avec redaction, évitez de logger :
- Numéro de téléphone
- Token JWT
- IBAN
- Coordonnées GPS précises
SnapLogs redact par regex, mais la meilleure redaction est celle qu'on n'écrit pas.
4. Sampler en production
Sur une app à 100k DAU, logger 100% des events génère 10M logs/jour. C'est inutile et coûteux. Samplez à 1-5% sur les info, gardez 100% sur error.
5. Batcher les envois
Un log par requête HTTP est prohibitif. SnapLogs batch automatiquement (50-200 logs par flush). Si vous appelez l'API directement, batchez côté client.
6. Attacher un session_id
Un session_id permet de suivre le parcours utilisateur. Ajoutez-le à tous les logs de la session :
final sessionId = SnapLogs.startSession();
// tous les logs suivants incluent session_id automatiquement
7. Gérer la hors-ligne
Le SDK SnapLogs met en file d'attente les logs hors ligne et les envoie dès la connexion revient. Si vous appelez l'API directement, persistez en SQLite.
8. Versionner le schema
Ajoutez schemaVersion dans les fields pour pouvoir migrer sans casser le parsing backend :
SnapLogs.log(
level: LogLevel.info,
message: 'payment_done',
fields: {'amount': 49.99, 'schemaVersion': 2},
);
Pièges à éviter
1. Logger en boucle
Un log dans un build() Flutter peut générer 1000 logs/seconde. Ne loggez jamais dans un build. Loggez dans des événements (tap, navigation, callback).
2. Logger des exceptions sans contexte
// Mal
catch (e) { SnapLogs.log(level: LogLevel.error, message: e.toString()); }
// Bien
catch (e, st) {
SnapLogs.log(
level: LogLevel.error,
message: 'payment_failed',
fields: {'amount': amount, 'method': method},
error: e,
stackTrace: st,
);
}
3. Confondre analytics et logging
L'analytics mesure le produit (conversion, funnel). Le logging debug le code (erreurs, flows). Ne mélangez pas : SnapLogs pour le debug, Amplitude pour le produit.
4. Oublier de tester le logger
Testez que vos logs partent en release mode. Le debug mode peut masquer des erreurs (permissions réseau différentes).
flutter run --release
Architecture d'un système de remote logging
Un remote logger moderne repose sur quatre couches.
Couche 1 : le SDK client
Le SDK tourne dans l'app. Il expose log(), captureException(), addBreadcrumb(). Il gère le buffering, le sampling, la redaction, et l'envoi.
Caractéristiques attendues :
- Thread-safe : pas de race condition sur le buffer.
- Non-bloquant : un log ne doit jamais geler l'UI.
- Persistant : les logs survivent à un kill de l'app.
- Configurable à distance : on peut changer le sampleRate sans redeploy.
Couche 2 : le transport
Le transport envoie les logs au backend. Trois options :
- HTTP POST batch : simple, universel.
- WebSocket : temps réel, plus cher.
- gRPC : performant, plus complexe.
SnapLogs utilise HTTP POST batch par défaut, avec une option WebSocket pour le temps réel.
Couche 3 : le backend d'ingestion
Le backend reçoit, valide, indexe, et stocke. Exigences :
- Throughput : >10k logs/seconde par tenant.
- Schema flexible : les fields sont libres (JSON).
- Indexation : par level, message, session_id, timestamp.
- Rétention : 30 à 90 jours par défaut.
SnapLogs indexe sur Elasticsearch managé. La recherche est全文.
Couche 4 : l'affichage et l'export
Les logs sont consultables dans le dashboard SnapLogs. Features :
- Recherche par level, message, field.
- Timeline par session_id.
- Export vers BigQuery, S3, Slack webhook.
Cette architecture en 4 couches est ce qui distingue un vrai remote logger d'un simple console.log pushé en HTTP.
Logs structurés vs logs textuels : exemple complet
Voyons un cas concret : tracer un flow de paiement.
Version textuelle (à éviter)
print('Starting payment for user 42');
print('Amount: 49.99 EUR, method: card');
print('Calling provider...');
print('Provider returned 200');
print('Payment done');
Côté backend, vous recevez 5 lignes de texte. Pour les filtrer, il faut parser. Pour les corréler, il faut deviner. C'est inutilisable à grande échelle.
Version structurée (recommandée)
SnapLogs.log(level: LogLevel.info, message: 'payment_start', fields: {'user_id': 42, 'amount': 49.99, 'currency': 'EUR', 'method': 'card'});
SnapLogs.log(level: LogLevel.info, message: 'provider_call', fields: {'provider': 'stripe', 'endpoint': '/charges'});
SnapLogs.log(level: LogLevel.info, message: 'provider_response', fields: {'status': 200, 'duration_ms': 412});
SnapLogs.log(level: LogLevel.info, message: 'payment_done', fields: {'user_id': 42, 'amount': 49.99, 'schemaVersion': 2});
Côté backend, vous pouvez :
- Filtrer tous les
payment_doneavecamount > 100. - Compter le temps moyen entre
payment_startetpayment_done. - Corréler un
payment_failedavec leprovider_responseprécédent.
Le structuré transforme les logs en données exploitables.
Logs et traces distribuées : corrélation multi-services
Le logging mobile prend tout son sens quand on le corrèle aux logs backend. Une erreur mobile « timeout » peut venir du backend, du CDN, ou du réseau opérateur. Sans corrélation, vous cherchez au mauvais endroit.
Trace ID et span ID
Le pattern standard : chaque requête mobile embarque un trace_id et un span_id :
import 'package:uuid/uuid.dart';
Future<void> tracedRequest(String url) async {
final traceId = const Uuid().v4();
final spanId = const Uuid().v4().substring(0, 16);
SnapLogs.log(
level: LogLevel.info,
message: 'http_request_start',
fields: {'trace_id': traceId, 'span_id': spanId, 'url': url},
);
final response = await http.post(
Uri.parse(url),
headers: {
'X-Trace-Id': traceId,
'X-Span-Id': spanId,
},
body: {...},
);
SnapLogs.log(
level: LogLevel.info,
message: 'http_request_end',
fields: {
'trace_id': traceId,
'status': response.statusCode,
'duration_ms': duration.inMilliseconds,
},
);
}
Côté backend (Node.js exemple) :
app.post('/charge', (req, res) => {
const traceId = req.headers['x-trace-id'];
logger.info({ message: 'charge_start', trace_id: traceId, user_id: req.body.user_id });
// ... process ...
logger.info({ message: 'charge_end', trace_id: traceId, status: 200 });
res.json({ ok: true });
});
Le trace_id permet de suivre le parcours complet : tap mobile, requête HTTP, traitement backend, réponse. En cas de bug, vous recherchez le trace_id dans SnapLogs et vous avez toute la timeline.
SnapLogs trace explorer
Le dashboard SnapLogs propose un trace explorer qui agrège les logs par trace_id. Vous voyez :
11:03:01 mobile http_request_start trace=abc123 url=/charge
11:03:01 backend charge_start trace=abc123 user_id=42
11:03:02 backend charge_end trace=abc123 status=200
11:03:02 mobile http_request_end trace=abc123 status=200
Cette corrélation multi-services est ce qui transforme un simple logger en outil d'observabilité complet.
OpenTelemetry compatibility
SnapLogs supporte le format OpenTelemetry pour les trace_id et span_id. Vous pouvez exporter les traces vers Jaeger, Zipkin, ou Honeycomb. Le SDK SnapLogs ne remplace pas un APM complet, mais il s'intègre dans une stack existante.
Logs et analytics : la frontière
Une confusion fréquente : faut-il logger les événements produit (click, conversion) dans SnapLogs ou dans un analytics tool ?
La règle
- Analytics (Amplitude, Mixpanel) : événements produit, funnels, conversion. Agregation, dashboards.
- Logging (SnapLogs) : événements debug, erreurs, flows techniques. Recherche, corrélation, incident response.
Ne mélangez pas. Un log de debug dans Amplitude pollue les dashboards produit. Un event de conversion dans SnapLogs pollue les recherches de bugs.
Quand les deux se croisent
Parfois, un event est les deux. Exemple : payment_failed. C'est un event produit (conversion ratée) et un event debug (erreur technique). La solution : logger dans les deux, avec le même trace_id.
await api.post('/charge', body: {...}).catchError((e) async {
await SnapLogs.log(
level: LogLevel.error,
message: 'payment_failed',
fields: {'trace_id': traceId, 'reason': e.code},
);
await Amplitude.track('payment_failed', properties: {'reason': e.code, 'trace_id': traceId});
});
Le trace_id permet de croiser les deux systèmes.
Combinaison avec un crash reporter
Le logging prend tout son sens combiné à un crash reporter. SnapLogs attache automatiquement les 50 derniers logs à un rapport de crash. Si vous utilisez Sentry en parallèle :
await SentryFlutter.init(
(options) {
options.dsn = const String.fromEnvironment('SENTRY_DSN');
options.beforeSend = (event) {
// Envoie aussi à SnapLogs pour avoir les logs en contexte
SnapLogs.log(
level: LogLevel.error,
message: 'sentry_event',
fields: {'event_id': event.eventId.toString()},
);
return event;
};
},
appRunner: () => runApp(const MyApp()),
);
Le crash remonte dans Sentry, les logs dans SnapLogs. Le ticket support contient les deux. Pour le détail, voir notre comparatif des bug trackers Flutter.
Conclusion
Le logging mobile moderne n'est plus une option en 2026. NSLog et Logcat bruts ne suffisent plus pour debugguer une app en production. Un logger moderne gère des niveaux, structure ses messages, redacte le PII, sample intelligemment, et pousse vers un backend centralisé. SnapLogs intègre toutes ces dimensions dans un seul SDK, pour Flutter, React Native, iOS et Android.
Pour aller plus loin :
- Consultez la documentation SnapLogs.
- Intégrez le SDK Flutter.
- Lisez notre guide complet du bug report Flutter et notre guide de debug Flutter en production.
Le logging n'est pas une étape de finition, c'est le socle de l'observabilité mobile. Sans lui, vous debugguez à l'aveugle.
Articles liés
Explorez la documentation
Guides complets, references API, exemples de code multiplateforme.
Lire la doc