Impostazione del livello di log del messaggio in fase di esecuzione in slf4j


100

Quando si utilizza log4j, il Logger.log(Priority p, Object message)metodo è disponibile e può essere utilizzato per registrare un messaggio a un livello di registro determinato in fase di esecuzione. Stiamo usando questo fatto e questo suggerimento per reindirizzare stderr a un logger a un livello di log specifico.

slf4j non ha un log()metodo generico che posso trovare. Ciò significa che non c'è modo di implementare quanto sopra?


4
Sembra che ci sia qualche discussione sull'aggiunta di questo a slf4j 2.0 nella mailing list degli sviluppatori: qos.ch/pipermail/slf4j-dev/2010-March/002865.html
Edward Dale

1
dai un'occhiata a Marker, si tratta di dati personalizzati che puoi passare alla catena di log.
tuxSlayer

1
@tuxSlayer puoi spiegarci come usare Marker in questo caso?
Miserabile variabile

Probabilmente non è l'idea migliore per la "registrazione", ma puoi utilizzare diversi marcatori per la "priorità" della voce di log (alta | bassa | normale, informazioni | avvertimento | fatale) e utilizzare il filtro nel logback o nell'appender personalizzato per consumare marcatori e voci di log dell'unità in canali separati (informazioni di registro, e-mail fatali ecc.). Tuttavia, il modo più diretto è avere una facciata per questo, come è stato indicato nelle risposte di seguito.
tuxSlayer

2
Questa funzione dovrebbe far parte di slf4j 2.0. jira.qos.ch/browse/SLF4J-124 Vedi la mia risposta per i dettagli e per una possibile slf4j 1.xsoluzione.
slartidan

Risposte:


47

Non c'è modo di farlo con slf4j.

Immagino che il motivo per cui manca questa funzionalità è che è quasi impossibile costruire un Leveltipo slf4jche può essere mappato in modo efficiente al tipo Level(o equivalente) utilizzato in tutte le possibili implementazioni di registrazione dietro la facciata. In alternativa, i progettisti hanno deciso che il tuo caso d'uso è troppo insolito per giustificare le spese generali di supportarlo.

Per quanto riguarda @ ripper234 's caso d'uso (unit testing), credo che la soluzione pragmatica è modificare il test di unità (s) alla conoscenza hard-wire di quale sistema di registrazione è dietro la facciata slf4j ... quando si eseguono i test di unità.


9
Non è necessaria alcuna mappatura. Ci sono cinque livelli già implicitamente definiti dai metodi in org.slf4j.Logger: debug, error, info, trace, warn.
Edward Dale

1
E i problemi sono stati chiusi come non validi. Per quanto ho capito, è una scelta progettuale deliberata.
ripper234

9
@ ripper234 - Non credo che il tuo bug abbia risolto lo stesso problema della domanda originale di scompt.com. Hai chiesto informazioni sulla configurazione del livello del sistema di registrazione sottostante tramite l'API SLF4J. Quello che scompt.com stava cercando era un metodo "log" generico nell'API SLF4J, che prende il livello di registrazione del messaggio come parametro.
Richard Fearn

1
+1 @RichardFearn E non si può annullare il commento positivo dopo 60 secondi, meh . Nel frattempo la richiesta di funzionalità esiste: bugzilla.slf4j.org/show_bug.cgi?id=133
gennaio

3
I collegamenti RFE non si risolvono più. I link rilevanti adesso sono: jira.qos.ch/browse/SLF4J-124 e jira.qos.ch/browse/SLF4J-197 ... ed entrambi sono stati chiusi. Leggi i commenti per la motivazione.
Stephen C

27

Richard Fearn ha l'idea giusta, quindi ho scritto l'intera classe basandomi sul suo codice di base. Si spera sia abbastanza breve da postare qui. Copia e incolla per divertirti. Probabilmente dovrei aggiungere anche qualche incantesimo magico: "Questo codice è rilasciato al pubblico dominio"

import org.slf4j.Logger;

public class LogLevel {

    /**
     * Allowed levels, as an enum. Import using "import [package].LogLevel.Level"
     * Every logging implementation has something like this except SLF4J.
     */

    public static enum Level {
        TRACE, DEBUG, INFO, WARN, ERROR
    }

    /**
     * This class cannot be instantiated, why would you want to?
     */

    private LogLevel() {
        // Unreachable
    }

    /**
     * Log at the specified level. If the "logger" is null, nothing is logged.
     * If the "level" is null, nothing is logged. If the "txt" is null,
     * behaviour depends on the SLF4J implementation.
     */

    public static void log(Logger logger, Level level, String txt) {
        if (logger != null && level != null) {
            switch (level) {
            case TRACE:
                logger.trace(txt);
                break;
            case DEBUG:
                logger.debug(txt);
                break;
            case INFO:
                logger.info(txt);
                break;
            case WARN:
                logger.warn(txt);
                break;
            case ERROR:
                logger.error(txt);
                break;
            }
        }
    }

    /**
     * Log at the specified level. If the "logger" is null, nothing is logged.
     * If the "level" is null, nothing is logged. If the "format" or the "argArray"
     * are null, behaviour depends on the SLF4J-backing implementation.
     */

    public static void log(Logger logger, Level level, String format, Object[] argArray) {
        if (logger != null && level != null) {
            switch (level) {
            case TRACE:
                logger.trace(format, argArray);
                break;
            case DEBUG:
                logger.debug(format, argArray);
                break;
            case INFO:
                logger.info(format, argArray);
                break;
            case WARN:
                logger.warn(format, argArray);
                break;
            case ERROR:
                logger.error(format, argArray);
                break;
            }
        }
    }

    /**
     * Log at the specified level, with a Throwable on top. If the "logger" is null,
     * nothing is logged. If the "level" is null, nothing is logged. If the "format" or
     * the "argArray" or the "throwable" are null, behaviour depends on the SLF4J-backing
     * implementation.
     */

    public static void log(Logger logger, Level level, String txt, Throwable throwable) {
        if (logger != null && level != null) {
            switch (level) {
            case TRACE:
                logger.trace(txt, throwable);
                break;
            case DEBUG:
                logger.debug(txt, throwable);
                break;
            case INFO:
                logger.info(txt, throwable);
                break;
            case WARN:
                logger.warn(txt, throwable);
                break;
            case ERROR:
                logger.error(txt, throwable);
                break;
            }
        }
    }

    /**
     * Check whether a SLF4J logger is enabled for a certain loglevel. 
     * If the "logger" or the "level" is null, false is returned.
     */

    public static boolean isEnabledFor(Logger logger, Level level) {
        boolean res = false;
        if (logger != null && level != null) {
            switch (level) {
            case TRACE:
                res = logger.isTraceEnabled();
                break;
            case DEBUG:
                res = logger.isDebugEnabled();
                break;
            case INFO:
                res = logger.isInfoEnabled();
                break;
            case WARN:
                res = logger.isWarnEnabled();
                break;
            case ERROR:
                res = logger.isErrorEnabled();
                break;
            }
        }
        return res;
    }
}

Questo sarebbe più facile da usare con un parametro args variadic (Object ...).
Anonymoose

"org.slf4j.Logger" ha alcune firme del metodo di registrazione che non sono gestite nella classe precedente, quindi un'estensione è probabilmente giustificata: slf4j.org/api/org/slf4j/Logger.html
David Tonhofer

1
Penso che questa implementazione aggiungerà una modifica non desiderata. Quando si utilizza il call logger.info (...), il logger ha accesso alla classe e al metodo del chiamante e potrebbe essere aggiunto automaticamente alla voce di registro. Ora, con questa implementazione, il registro delle chiamate (logger, level, txt) produrrà una voce di registro che avrà sempre lo stesso chiamante: Loglevel.log. Ho ragione?
Domin

@Domin Salve, vuoi dire, il logger potrebbe esaminare lo stack di chiamate corrente, quindi estrarre l'ultima voce per la registrazione automatica, che non è il caso qui? In linea di principio sì, ma in realtà, lo stack crescerà un po 'di più anche dopo questo fino a quando il messaggio effettivo non viene scritto (in particolare, ad un certo punto deve essere chiamato il logback, quindi l'appender effettivo). Penso che dovrebbe essere il ruolo dell'appender eliminare le linee di stack non interessanti, quindi potresti adattarlo per buttare via tutto fino alla chiamata a questa classe Loglevel inclusa.
David Tonhofer

@David, sì, hai ragione :-). Non sono sicuro che sia un compito per l'appender perché in quel caso stai definendo una forte dipendenza tra l'appender e il logger ... ma ... è una soluzione. Grazie David
Domin

14

Prova a passare a Logback e usa

ch.qos.logback.classic.Logger rootLogger = (ch.qos.logback.classic.Logger)LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(Level.toLevel("info"));

Credo che questa sarà l'unica chiamata a Logback e il resto del codice rimarrà invariato. Logback utilizza SLF4J e la migrazione sarà indolore, solo i file di configurazione xml dovranno essere modificati.

Ricordarsi di reimpostare il livello di registro dopo aver finito.


Stavo già usando slf4j supportato da Logback e questo mi ha immediatamente permesso di ripulire i miei test unitari. Grazie!
Lambart

2
Questo è stato il mio primo -1, grazie. Credo che ti sbagli. Logback utilizza SLF4J, quindi la risposta È pertinente.
Αλέκος

3
@AlexandrosGelbessis Dovresti rileggere la domanda. È stato richiesto un metodo che potesse registrare a livello di codice un messaggio di registro a qualsiasi livello. Stai cambiando il livello del logger root per tutti i messaggi, non solo per uno.
gennaio

12

Puoi implementarlo usando Java 8 lambda.

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.event.Level;

public class LevelLogger {
    private static final Logger LOGGER = LoggerFactory.getLogger(LevelLogger.class);
    private static final Map<Level, LoggingFunction> map;

    static {
        map = new HashMap<>();
        map.put(Level.TRACE, (o) -> LOGGER.trace(o));
        map.put(Level.DEBUG, (o) -> LOGGER.debug(o));
        map.put(Level.INFO, (o) -> LOGGER.info(o));
        map.put(Level.WARN, (o) -> LOGGER.warn(o));
        map.put(Level.ERROR, (o) -> LOGGER.error(o));
    }

    public static void log(Level level, String s) {
        map.get(level).log(s);
    }

    @FunctionalInterface
    private interface LoggingFunction {
        public void log(String arg);
    }
}

Ebbene sì ... ma ora è necessario modificare la base del codice per utilizzare questa API così come o al posto di slf4j. Se lo usi al posto di slf4j 1) probabilmente deve essere più ricco, 2) molte (almeno) importazioni devono essere cambiate e 3) questo nuovo livello davanti a slf4j aggiunge un sovraccarico di registrazione extra.
Stephen C

4
Tieni anche presente che quando scegli questa soluzione, la classe che esegue la registrazione effettiva non verrà registrata (perché il logger è inizializzato con LevelLogger), il che non è una buona cosa perché generalmente è un'informazione molto utile.
Dormouse

6

Questo può essere fatto con un enume un metodo di supporto:

enum LogLevel {
    TRACE,
    DEBUG,
    INFO,
    WARN,
    ERROR,
}

public static void log(Logger logger, LogLevel level, String format, Object[] argArray) {
    switch (level) {
        case TRACE:
            logger.trace(format, argArray);
            break;
        case DEBUG:
            logger.debug(format, argArray);
            break;
        case INFO:
            logger.info(format, argArray);
            break;
        case WARN:
            logger.warn(format, argArray);
            break;
        case ERROR:
            logger.error(format, argArray);
            break;
    }
}

// example usage:
private static final Logger logger = ...
final LogLevel level = ...
log(logger, level, "Something bad happened", ...);

Potresti aggiungere altre varianti di log, ad esempio, se desideri equivalenti generici di SLF4J a 1 parametro o 2 parametri warn/ error/ ecc. metodi.


3
Vero, ma lo scopo di slf4j non è scrivere wrapper di log.
djjeck

5
Lo scopo di SLF4J è fornire un'astrazione per diversi framework di registrazione. Se quell'astrazione non fornisce esattamente ciò di cui hai bisogno, non hai altra scelta che scrivere un metodo di supporto. L'unica altra alternativa è contribuire con un metodo come quello nella mia risposta al progetto SLF4J.
Richard Fearn

Sono d'accordo, ma in questo caso ci sono avvertenze, come quella che non saresti più in grado di fornire il numero di file e di riga, a meno che tu non abbia implementato ancora un'altra soluzione alternativa per questo. In questo caso sarei rimasto con log4j, fino a quando il framework non avesse supportato la funzionalità, cosa che alla fine è avvenuta attraverso un'estensione, vedi la risposta più recente di Robert Elliot.
djjeck


3

Avevo solo bisogno di qualcosa del genere e ho pensato:

@RequiredArgsConstructor //lombok annotation
public enum LogLevel{

    TRACE(l -> l::trace),
    INFO (l -> l::info),
    WARN (l -> l::warn),
    ERROR(l -> l::error);

    private final Function<Logger, Consumer<String>> function;

    public void log(Logger logger, String message) {
        function.apply(logger).accept(message);
    }
}

utilizzo:

    LogLevel level = LogLevel.TRACE;
    level.log(logger, "message");

Il logger viene passato durante l'invocazione, quindi le informazioni sulla classe dovrebbero essere ok e funziona bene con l'annotazione lombok @ Slf4j.


Grazie mille per questo fantastico approccio - ho pubblicato una risposta simile, basata sulla tua idea.
slartidan

DEBUGmanca come costante.
slartidan

Questa soluzione registrerà sempre LogLevelcome classe e logcome metodo, il che rende i log meno significativi.
slartidan

2

E ' non è possibile specificare un livello di log in sjf4j 1.xfuori dalla scatola. Ma c'è speranza per slf4j 2.0per risolvere il problema . Nella versione 2.0 potrebbe apparire così:

// POTENTIAL 2.0 SOLUTION
import org.slf4j.helpers.Util;
import static org.slf4j.spi.LocationAwareLogger.*;

// does not work with slf4j 1.x
Util.log(logger, DEBUG_INT, "hello world!");

Nel frattempo, per slf4j 1.x, puoi usare questa soluzione alternativa:

Copia questa classe nel tuo classpath:

import org.slf4j.Logger;
import java.util.function.Function;

public enum LogLevel {

    TRACE(l -> l::trace, Logger::isTraceEnabled),
    DEBUG(l -> l::debug, Logger::isDebugEnabled),
    INFO(l -> l::info, Logger::isInfoEnabled),
    WARN(l -> l::warn, Logger::isWarnEnabled),
    ERROR(l -> l::error, Logger::isErrorEnabled);

    interface LogMethod {
        void log(String format, Object... arguments);
    }

    private final Function<Logger, LogMethod> logMethod;
    private final Function<Logger, Boolean> isEnabledMethod;

    LogLevel(Function<Logger, LogMethod> logMethod, Function<Logger, Boolean> isEnabledMethod) {
        this.logMethod = logMethod;
        this.isEnabledMethod = isEnabledMethod;
    }

    public LogMethod prepare(Logger logger) {
        return logMethod.apply(logger);
    }

    public boolean isEnabled(Logger logger) {
        return isEnabledMethod.apply(logger);
    }
}

Quindi puoi usarlo in questo modo:

Logger logger = LoggerFactory.getLogger(Application.class);

LogLevel level = LogLevel.ERROR;
level.prepare(logger).log("It works!"); // just message, without parameter
level.prepare(logger).log("Hello {}!", "world"); // with slf4j's parameter replacing

try {
    throw new RuntimeException("Oops");
} catch (Throwable t) {
    level.prepare(logger).log("Exception", t);
}

if (level.isEnabled(logger)) {
    level.prepare(logger).log("logging is enabled");
}

Questo produrrà un registro come questo:

[main] ERROR Application - It works!
[main] ERROR Application - Hello world!
[main] ERROR Application - Exception
java.lang.RuntimeException: Oops
    at Application.main(Application.java:14)
[main] ERROR Application - logging is enabled

Ne vale la pena?

  • ProMantiene la posizione del codice sorgente (i nomi delle classi, i nomi dei metodi, numeri di riga punterà al vostro codice)
  • ProÈ possibile definire facilmente variabili , parametri e tipi restituiti comeLogLevel
  • ProIl codice aziendale rimane breve e di facile lettura e non sono necessarie dipendenze aggiuntive.

Il codice sorgente come esempio minimo è ospitato su GitHub .


Nota: l' LogMethodinterfaccia deve essere pubblica perché funzioni con classi esterne al suo pacchetto. Oltre a questo, funziona come previsto. Grazie!
andrebrait

1

Non è possibile con l'API slf4j cambiare dinamicamente il livello di log ma puoi configurare il logback (se lo usi) da solo. In tal caso, creare una classe factory per il logger e implementare il logger di root con la configurazione necessaria.

LoggerContext loggerContext = new LoggerContext();
ch.qos.logback.classic.Logger root = loggerContext.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);

// Configure appender
final TTLLLayout layout = new TTLLLayout();
layout.start(); // default layout of logging messages (the form that message displays 
// e.g. 10:26:49.113 [main] INFO com.yourpackage.YourClazz - log message

final LayoutWrappingEncoder<ILoggingEvent> encoder = new LayoutWrappingEncoder<>();
encoder.setCharset(StandardCharsets.UTF_8);
encoder.setLayout(layout);

final ConsoleAppender<ILoggingEvent> appender = new ConsoleAppender<>();
appender.setContext(loggerContext);
appender.setEncoder(encoder);
appender.setName("console");
appender.start();

root.addAppender(appender);

Dopo aver configurato il logger di root (solo una volta è sufficiente) puoi delegare l'acquisizione di un nuovo logger tramite

final ch.qos.logback.classic.Logger logger = loggerContext.getLogger(clazz);

Ricorda di usare lo stesso loggerContext.

Cambiare il livello di log è facile con il root logger fornito da loggerContext.

root.setLevel(Level.DEBUG);

1

Conferma la risposta Ondrej Skopek

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import org.slf4j.LoggerFactory;

var rootLogger = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.setLevel(Level.TRACE);

Otterrai risultato:

14/05/2020 14: 01: 16,644 TRACE [] [oakcmMetrics] Test worker Metrica registrata denominata MetricName [name = bufferpool-wait-time-total, group = producer-metrics, description = Il tempo totale che un appender attende per l'allocazione dello spazio ., tags = {client-id = producer-2}]


0

Ho appena riscontrato un bisogno simile. Nel mio caso, slf4j è configurato con l'adattatore di registrazione java (quello jdk14). Utilizzando il seguente frammento di codice sono riuscito a modificare il livello di debug in fase di esecuzione:

Logger logger = LoggerFactory.getLogger("testing");
java.util.logging.Logger julLogger = java.util.logging.Logger.getLogger("testing");
julLogger.setLevel(java.util.logging.Level.FINE);
logger.debug("hello world");

1
Come altre risposte, questa non affronta la domanda originale, è un problema diverso.
E-Riz

0

Sulla base della risposta di massimo virgilio, sono riuscito a farlo anche con slf4j-log4j usando l'introspezione. HTH.

Logger LOG = LoggerFactory.getLogger(MyOwnClass.class);

org.apache.logging.slf4j.Log4jLogger LOGGER = (org.apache.logging.slf4j.Log4jLogger) LOG;

try {
    Class loggerIntrospected = LOGGER.getClass();
    Field fields[] = loggerIntrospected.getDeclaredFields();
    for (int i = 0; i < fields.length; i++) {
        String fieldName = fields[i].getName();
        if (fieldName.equals("logger")) {
            fields[i].setAccessible(true);
            org.apache.logging.log4j.core.Logger loggerImpl = (org.apache.logging.log4j.core.Logger) fields[i].get(LOGGER);
            loggerImpl.setLevel(Level.DEBUG);
        }
    }
} catch (Exception e) {
    System.out.println("ERROR :" + e.getMessage());
}

0

Ecco una soluzione lambda non così user-friendly come quella di @Paul Croarkin in un modo (il livello viene effettivamente superato due volte). Ma penso che (a) l'utente dovrebbe passare il Logger; e (b) AFAIU la domanda originale non chiedeva un modo conveniente per ovunque nell'applicazione, ma solo una situazione con pochi utilizzi all'interno di una libreria.

package test.lambda;
import java.util.function.*;
import org.slf4j.*;

public class LoggerLambda {
    private static final Logger LOG = LoggerFactory.getLogger(LoggerLambda.class);

    private LoggerLambda() {}

    public static void log(BiConsumer<? super String, ? super Object[]> logFunc, Supplier<Boolean> logEnabledPredicate, 
            String format, Object... args) {
        if (logEnabledPredicate.get()) {
            logFunc.accept(format, args);
        }
    }

    public static void main(String[] args) {
        int a = 1, b = 2, c = 3;
        Throwable e = new Exception("something went wrong", new IllegalArgumentException());
        log(LOG::info, LOG::isInfoEnabled, "a = {}, b = {}, c = {}", a, b, c);

        // warn(String, Object...) instead of warn(String, Throwable), but prints stacktrace nevertheless
        log(LOG::warn, LOG::isWarnEnabled, "error doing something: {}", e, e);
    }
}

Poiché slf4j consente un Throwable (la cui traccia dello stack dovrebbe essere registrata) all'interno del parametro varargs , penso che non sia necessario sovraccaricare il logmetodo helper per altri consumatori rispetto a (String, Object[]).


0

Sono stato in grado di farlo per l'associazione JDK14 richiedendo prima l'istanza del logger SLF4J e quindi impostando il livello sull'associazione: puoi provare questo per l'associazione Log4J.

private void setLevel(Class loggerClass, java.util.logging.Level level) {
  org.slf4j.LoggerFactory.getLogger(loggerClass);
  java.util.logging.Logger.getLogger(loggerClass.getName()).setLevel(level);
}

0

Il metodo che utilizzo è importare i moduli ch.qos.logback e quindi eseguire il cast dell'istanza Logger slf4j in un ch.qos.logback.classic.Logger. Questa istanza include un metodo setLevel ().

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;

Logger levelSet = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);

// Now you can set the desired logging-level
levelSet.setLevel( Level.OFF );

Per scoprire i possibili livelli di registrazione, puoi esplodere la classe ch.qos.logback per vedere tutti i possibili valori per Level :

prompt$ javap -cp logback-classic-1.2.3.jar ch.qos.logback.classic.Level

I risultati sono i seguenti:

{
   // ...skipping
   public static final ch.qos.logback.classic.Level OFF;
   public static final ch.qos.logback.classic.Level ERROR;
   public static final ch.qos.logback.classic.Level WARN;
   public static final ch.qos.logback.classic.Level INFO;
   public static final ch.qos.logback.classic.Level DEBUG;
   public static final ch.qos.logback.classic.Level TRACE;
   public static final ch.qos.logback.classic.Level ALL;
}

-2

usando java introspezione puoi farlo, ad esempio:

private void changeRootLoggerLevel(int level) {

    if (logger instanceof org.slf4j.impl.Log4jLoggerAdapter) {
        try {
            Class loggerIntrospected = logger.getClass();
            Field fields[] = loggerIntrospected.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) {
                String fieldName = fields[i].getName();
                if (fieldName.equals("logger")) {
                    fields[i].setAccessible(true);
                    org.apache.log4j.Logger loggerImpl = (org.apache.log4j.Logger) fields[i]
                            .get(logger);

                    if (level == DIAGNOSTIC_LEVEL) {
                        loggerImpl.setLevel(Level.DEBUG);
                    } else {
                        loggerImpl.setLevel(org.apache.log4j.Logger.getRootLogger().getLevel());
                    }

                    // fields[i].setAccessible(false);
                }
            }
        } catch (Exception e) {
            org.apache.log4j.Logger.getLogger(LoggerSLF4JImpl.class).error("An error was thrown while changing the Logger level", e);
        }
    }

}

5
Questo si riferisce esplicitamente a log4j e non a slf4j genericamente
Thorbjørn Ravn Andersen

-6

no, ha un numero di metodi, info (), debug (), warn (), ecc (questo sostituisce il campo priorità)

dai un'occhiata a http://www.slf4j.org/api/org/slf4j/Logger.html per l'API Logger completa.


scusa, capisco cosa stai chiedendo ora. no, non esiste un modo generico per modificare il livello di log in fase di esecuzione, ma potresti facilmente implementare un metodo di supporto con un'istruzione switch.
chris

Sì, ma poi devi farlo una volta per ogni versione sovraccaricata del metodo "log".
Andrew Swan
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.