
Défi
Disons que nous voulions enregistrer différemment chaque méthode d'une certaine classe Java:
- Chaque méthode a son propre fichier journal,
- ... votre format de journal,
- ... son niveau minimum de journalisation,
- nous développons le format de journal avec nos propres
%
, - possibilité de mettre à jour cette configuration à la volée.
Cet article montre comment remplir ces conditions. Afin de maintenir la simplicité, la séparation de la journalisation est effectuée uniquement par des méthodes; en réalité, vous voudrez peut-être avoir une configuration de qualification hiérarchique, comme
→
→
→
... Un lien vers le code source complet sera ci-dessous.
Code client
class ThingService { log = LoggerFactory.getLogger(); getThing() { log.debug("getThing...");
Déconnexion
Pour l'implémentation, une solide bibliothèque de journalisation "logback" a été sélectionnée, ce qui offre des possibilités de personnalisation intéressantes:
ch.qos.logback:logback-classic:1.2.3
Il est configuré à la fois à partir d'une configuration XML et directement à partir de Java, les approches peuvent être combinées:
public void configureLogback() throws JoranException { LoggerContext lc = LoggerFactory.getILoggerFactory(); lc.reset();
En bref sur la journalisation:
- Le programmeur tire l' enregistreur ,
- L'enregistreur tire les appendices qui lui sont assignés,
- L'appendice pense et appelle l' encodeur ,
- L'encodeur formate exactement une ligne du journal,
- Pour ce faire, il tire une chaîne de convertisseurs dont chacun révèle sa
%
, - Succès.
Pour plus de simplicité, une configuration Java pure a été choisie. Tout est assez évident ici si vous gardez à l'esprit la configuration XML. La tâche principale est de créer votre propre appender / encodeur et de les enregistrer - ils seront appelés par la déconnexion de leurs entrailles. Presque chaque objet que vous créez doit être rappelé pour commencer à utiliser la méthode start()
. Exemple abstrait:
Logger rootLogger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); LoggerContext lc = rootLogger.getLoggerContext(); lc.reset();
Séparez les méthodes enregistrées les unes des autres
Pour que la déconnexion puisse distinguer une méthode d'une autre, avant d'appeler la méthode, enregistrez son nom dans le contexte de diagnostic ThreadLocal
. De plus, pendant l'analyse, nous n'obtenons pas ces valeurs directement de la classe MDC
, car le code de journalisation sera exécuté dans un autre thread et ces données ne seront pas là - nous les obtenons via ILoggingEvent.getMDCPropertyMap()
.
Dans le cas général, comme vooft l'a correctement noté, vous devez prendre en charge la pile d'appels et ne pas écraser la valeur MDC, mais la ramener à l'image précédente, ce qui se fait par l'introduction d'un nouveau ThreadLocal
. Exemple schématique:
try { MDC.put(MDC_KEY_METHOD, currentMethod);
Propre fichier journal pour chaque méthode
Créons et n'oubliez pas d'enregistrer votre propre appender:
class MultiAppender extends AppenderBase<ILoggingEvent> { @Override protected void append(ILoggingEvent event) { method = event.getMDCPropertyMap().get(MDC_KEY_METHOD); Appender appender = getOrCreateAppender(method); appender.doAppend(event); }
Lui-même ne fait presque rien, ne délègue que la connexion à un pack de vrais ajouts de fichiers, un pour chaque méthode. Délégué à l'un, le plus approprié. Les "vrais" appenders sont créés à la demande, comme suit:
fileAppender = new FileAppender<ILoggingEvent>(); fileAppender.setContext(lc); fileAppender.setAppend(false); fileAppender.setEncoder(getOrCreateEncoderByMethod(lc, method)); fileAppender.setFile(logFileByMethod.get(method)); fileAppender.start();
Pour ce faire, conservez le cache des objets créés automatiquement de type Encoder
:
Map<String, String> patternByMethod = new HashMap<>();
Chaque méthode a son propre niveau de journalisation
MultiAppender
ajoutons une vérification à la classe MultiAppender
: si le niveau d'événement MultiAppender
seuil spécifié pour la méthode, alors seulement nous l'enregistrons:
Map<String, Level> levelByMethod = new HashMap<>(); protected void append(ILoggingEvent event) { Level minLevel = levelByMethod.get(methodName); if (event.getLevel().levelInt >= minLevel.levelInt) { appender.doAppend(event); }
En principe, cette logique peut être mise dans le filtre.
Afin de ne pas clôturer votre jardin, mais pour profiter de l'infrastructure éprouvée de la déconnexion, vous devez définir votre propre classe de convertisseur, assurez-vous de la rendre entièrement publique afin qu'elle puisse être instanciée de l'extérieur. Si vous avez besoin de MDC
, MDC
-le de l'événement. Le gestionnaire de variable %custom
commence ici:
public class CustomConverter extends ClassicConverter { public String convert(ILoggingEvent event) {
Au cours du processus de configuration générale, enregistrez le gestionnaire:
void configurePatterns(LoggerContext lc) { Map<String, String> rules = lc.getObject(CoreConstants.PATTERN_RULE_REGISTRY); if (rules == null) { rules = new HashMap<String, String>(); lc.putObject(CoreConstants.PATTERN_RULE_REGISTRY, rules); } rules.put("custom", CustomConverter.class.getName()); }
Et nous utiliserons comme encodeur, par exemple, PatternLayoutEncoder
, qui récupérera tout. Dans ce cas, la variable %custom
se développera dans la chaîne "variable-expanded"
.
Mise à jour de la configuration à la volée
Il y a une telle opportunité hors de la boîte: il suffit d'appeler à nouveau la fonction configurateur, sans oublier d'y faire LoggerContext::reset()
et de vider le cache accumulé.
Multithreading
Si la méthode que nous avons configurée donne vie à de nouveaux threads, alors, bien sûr, les règles de journalisation spécifiées ne leur seront pas applicables - le thread local'ys n'apparaîtra pas d'eux-mêmes dans le nouveau thread. Donc, si vous souhaitez appliquer les paramètres de méthode à un nouveau flux, vous devez y copier le MDC
:
Map<String, String> mdcOrig = MDC.getCopyOfContextMap(); ExecutorService es = Executors.newFixedThreadPool(1); es.submit(() -> threadWorker(mdcOrig)); void threadWorker(Map<String, String> parentMdc) { MDC.setContextMap(parentMdc); log.error("expected to appear in method2*.log"); }
Exemple entier
https://github.com/zencd/logback-setup
Littérature
Manuel officiel de reconnexion