Câu hỏi Đặt mức nhật ký của thông báo khi chạy trong slf4j


Khi sử dụng log4j, Logger.log(Priority p, Object message) phương thức có sẵn và có thể được sử dụng để ghi nhật ký thư ở cấp nhật ký được xác định khi chạy. Chúng tôi đang sử dụng thực tế này và mẹo này để chuyển hướng stderr tới trình ghi nhật ký ở cấp nhật ký cụ thể.

slf4j không có chung log() phương pháp mà tôi có thể tìm thấy. Điều đó có nghĩa là không có cách nào để thực hiện ở trên?


76
2018-04-12 11:39


gốc


Có vẻ như có một số cuộc thảo luận về việc thêm điều này vào slf4j 2.0 trên danh sách gửi thư dev: qos.ch/pipermail/slf4j-dev/2010-March/002865.html - scompt.com
Đã đăng sự cố: bugzilla.slf4j.org/show_bug.cgi?id=206 - ripper234
hãy xem Marker, đây là dữ liệu tùy chỉnh mà bạn có thể chuyển sang chuỗi nhật ký. - tuxSlayer
@tuxSlayer bạn có thể vui lòng giải thích cách sử dụng Marker trong trường hợp này không? - Miserable Variable
Có lẽ nó không phải là ý tưởng tốt nhất cho "đăng nhập" nhưng bạn có thể sử dụng một số dấu cho mục nhập "ưu tiên" (cao | thấp | bình thường, thông tin | cảnh báo | gây tử vong) và sử dụng bộ lọc trong logback hoặc appender tùy chỉnh để tiêu thụ đánh dấu và mục nhập nhật ký ổ đĩa vào các kênh riêng biệt (thông tin nhật ký, email chết người, vv). Tuy nhiên, cách thẳng thắn hơn là có mặt tiền cho điều này như được chỉ ra trong các câu trả lời dưới đây. - tuxSlayer


Các câu trả lời:


Không có cách nào để làm điều này với slf4j.

Tôi tưởng tượng rằng lý do mà chức năng này bị thiếu là nó là bên cạnh không thể xây dựng một Level loại cho slf4j có thể được ánh xạ hiệu quả đến Level (hoặc tương đương) loại được sử dụng trong tất cả các triển khai khai thác gỗ có thể có đằng sau mặt tiền. Ngoài ra, các nhà thiết kế đã quyết định rằng trường hợp sử dụng của bạn quá khác thường để biện minh cho tổng chi phí hỗ trợ nó.

Liên quan @ ripper234'S trường hợp sử dụng (thử nghiệm đơn vị), tôi nghĩ giải pháp thực dụng là sửa đổi (các) bài kiểm tra đơn vị thành kiến ​​thức dây cứng về hệ thống ghi nhật ký nào nằm phía sau mặt tiền slf4j ... khi chạy thử nghiệm đơn vị.


36
2018-04-12 12:36



Không có bản đồ cần thiết thực sự. Có năm cấp độ đã được xác định ngầm bởi các phương thức trong org.slf4j.Logger: gỡ lỗi, lỗi, thông tin, theo dõi, cảnh báo. - scompt.com
WTF? SLF4J được báo trước là tương lai của việc ghi nhật ký của java, và trường hợp sử dụng đơn giản này không được hỗ trợ? (Điều đầu tiên tôi đã cố gắng làm sau khi tích hợp SLF4J ... để giúp giảm sự lộn xộn trong các bài kiểm tra đơn vị). - ripper234
Đăng vấn đề này như một vấn đề: bugzilla.slf4j.org/show_bug.cgi?id=206 - ripper234
@ ripper234 - Tôi không nghĩ rằng lỗi của bạn đã giải quyết vấn đề tương tự như câu hỏi ban đầu của scompt.com. Bạn đã hỏi về việc định cấu hình cấp độ của hệ thống khai thác cơ bản thông qua API SLF4J. Scompt.com là gì sau khi là một phương thức 'log' chung trong API SLF4J, điều này có mức ghi của tin nhắn như một tham số. - Richard Fearn
Các liên kết RFE không giải quyết được nữa. Các liên kết có liên quan hiện là: jira.qos.ch/browse/SLF4J-124 và jira.qos.ch/browse/SLF4J-197 ... và cả hai đã bị đóng. Đọc các ý kiến ​​cho lý do. - Stephen C


Richard Fearn có ý tưởng đúng, vì vậy tôi đã viết lên lớp học đầy đủ dựa trên mã bộ xương của anh ấy. Nó hy vọng đủ ngắn để đăng ở đây. Sao chép và dán để thưởng thức. Tôi có lẽ nên thêm một số câu thần chú ma thuật nữa: "Mã này được phát hành cho miền công cộng"

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;
    }
}

21
2017-10-19 23:41



Điều này sẽ dễ sử dụng hơn với tham số argd (Object ...) args. - Anonymoose
"org.slf4j.Logger" có một vài chữ ký phương thức ghi nhật ký không được xử lý trong lớp ở trên, vì vậy có thể bảo hành một phần mở rộng: slf4j.org/api/org/slf4j/Logger.html - David Tonhofer
Tôi nghĩ rằng việc triển khai này sẽ thêm một thay đổi không mong muốn. Khi bạn sử dụng cuộc gọi logger.info (...) trình ghi có quyền truy cập vào lớp người gọi và phương thức và nó có thể được thêm vào mục nhập nhật ký một cách tự động. Bây giờ, với việc thực hiện này, nhật ký cuộc gọi (logger, level, txt) sẽ tạo ra một mục nhật ký sẽ luôn có cùng một người gọi: Loglevel.log. Tôi có đúng không? - Domin
@ Domin Hi, bạn có nghĩa là, các logger có thể kiểm tra ngăn xếp cuộc gọi hiện tại, sau đó trích xuất các mục nhập cuối cùng cho tự động đăng nhập, mà không phải là trường hợp ở đây? Về nguyên tắc có, nhưng trên thực tế, ngăn xếp sẽ grove nhiều hơn một chút ngay cả sau này cho đến khi thông điệp thực tế được viết ra (đặc biệt, logback phải được gọi tại một số điểm, sau đó một appender thực tế). Tôi nghĩ rằng nó sẽ là vai trò của appender để vứt bỏ các dòng stack không thú vị, vì vậy bạn có thể thích ứng với nó để ném tất cả mọi thứ đi đến và bao gồm cả các cuộc gọi đến lớp Loglevel này. - David Tonhofer
@ David, Có, bạn là đúng :-). Tôi không chắc chắn rằng nó là một nhiệm vụ cho appender bởi vì trên trường hợp đó bạn đang xác định một sự phụ thuộc cứng giữa appender và logger ... nhưng ... nó là một giải pháp. Cảm ơn David - Domin


Bạn có thể thực hiện điều này bằng cách sử dụng Java 8 lambdas.

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);
    }
}

9
2018-03-10 17:01





Thử chuyển sang Logback và sử dụng

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"));

Tôi tin rằng đây sẽ là cuộc gọi duy nhất để Logback và phần còn lại của mã của bạn sẽ không thay đổi. Logback sử dụng SLF4J và việc di chuyển sẽ không đau, chỉ cần các file cấu hình xml sẽ phải được thay đổi.

Hãy nhớ đặt mức nhật ký trở lại sau khi bạn hoàn tất.


9
2018-01-18 13:20



Tôi đã sử dụng slf4j hậu thuẫn, và điều này ngay lập tức cho phép tôi dọn dẹp các bài kiểm tra đơn vị của mình. Cảm ơn! - Lambart
-1 Đây không phải là câu hỏi này là gì. - jan
Đây là lần đầu tiên của tôi -1, cảm ơn. Tôi tin rằng bạn đã sai. Logback sử dụng SLF4J, vì vậy câu trả lời có liên quan. - Αλέκος
@AlexandrosGelbessis Bạn nên đọc lại câu hỏi. Nó đã được yêu cầu cho một phương pháp có thể đăng nhập một thông điệp tường trình theo chương trình ở mọi cấp độ. Bạn đang thay đổi cấp độ của trình ghi nhật ký gốc cho tất cả thư, không chỉ cho một thư. - jan
1 cho jan, lời xin lỗi của tôi. - Αλέκος


Điều này có thể được thực hiện với một enum và một phương thức trợ giúp:

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", ...);

Bạn có thể thêm các biến thể khác của log, nếu bạn muốn tương đương chung của tham số 1-tham số 1 hoặc SLF4J của SLF4J warn/error/ etc phương pháp.


6
2018-05-25 21:46



Đúng, nhưng mục đích slf4j không phải viết trình bao bọc nhật ký. - djjeck
Mục đích của SLF4J là cung cấp một trừu tượng cho các khung công tác ghi nhật ký khác nhau. Nếu trừu tượng đó không cung cấp chính xác những gì bạn cần, bạn không có lựa chọn nào ngoài việc viết một phương thức trợ giúp. Cách khác duy nhất là đóng góp một phương thức giống như phương án trong câu trả lời của tôi cho dự án SLF4J. - Richard Fearn
Tôi đồng ý, nhưng trong trường hợp này, hãy cẩn thận, vì vậy bạn sẽ không thể cung cấp tệp và số dòng nữa, trừ khi bạn đã thực hiện một cách giải quyết khác cho điều đó. Trong trường hợp này, tôi sẽ bị mắc kẹt với log4j, cho đến khi khung công tác hỗ trợ tính năng - mà cuối cùng đã xảy ra thông qua một phần mở rộng, xem câu trả lời gần đây của Robert Elliot. - djjeck


Bất cứ ai muốn có một giải pháp tương thích SLF4J đầy đủ cho vấn đề này có thể muốn kiểm tra Lidalia SLF4J Tiện ích mở rộng - nó ở Maven Central.


4
2018-03-28 13:14





Tôi vừa mới gặp phải một nhu cầu tương tự. Trong trường hợp của tôi, slf4j được cấu hình với bộ chuyển đổi ghi nhật ký java (bộ điều hợp jdk14). Sử dụng đoạn mã sau tôi đã quản lý để thay đổi mức gỡ lỗi khi chạy:

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
2018-06-11 03:33



Giống như các câu trả lời khác, điều này không giải quyết được câu hỏi ban đầu, đó là một vấn đề khác. - E-Riz


Dựa trên câu trả lời của massimo virgilio, tôi cũng đã quản lý để làm điều đó với slf4j-log4j bằng cách sử dụng nội tâm. 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
2018-02-24 12:39





Dưới đây là một giải pháp lambda không thân thiện với người dùng như là @Paul Croarkin theo một cách (cấp độ được chuyển hiệu quả hai lần). Nhưng tôi nghĩ (a) người dùng nên vượt qua Logger; và (b) AFAIU câu hỏi ban đầu đã không yêu cầu một cách thuận tiện cho mọi nơi trong ứng dụng, chỉ có một tình huống với vài tập quán trong thư viện.

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);
    }
}

Kể từ slf4j cho phép một Throwable (có stack trace nên được ghi lại) bên trong tham số varargs, Tôi nghĩ không cần quá tải logphương pháp trợ giúp cho người tiêu dùng khác (String, Object[]).


0
2017-07-29 09:30