2009-06-03 32 views
21

Tôi cần một cái gì đó tương tự như phương pháp String.format(...), nhưng với đánh giá lười biếng.String.format với đánh giá lười biếng

Phương thức lazyFormat này sẽ trả về một số đối tượng có phương thức toString() sau đó sẽ đánh giá mẫu định dạng.

Tôi nghi ngờ rằng ai đó đã thực hiện việc này. Điều này có sẵn trong bất kỳ libararies?

Tôi muốn thay thế này (logger là dụ log4j):

if(logger.isDebugEnabled()) { 
    logger.debug(String.format("some texts %s with patterns %s", object1, object2)); 
} 

với điều này:

logger.debug(lazyFormat("some texts %s with patterns %s", object1, object2)); 

Tôi cần lazyFormat để định dạng chuỗi chỉ khi debug logging được kích hoạt.

Trả lời

22

nếu bạn đang tìm kiếm một giải pháp "đơn giản":

public class LazyFormat { 

    public static void main(String[] args) { 
     Object o = lazyFormat("some texts %s with patterns %s", "looong string", "another loooong string"); 
     System.out.println(o); 
    } 

    private static Object lazyFormat(final String s, final Object... o) { 
     return new Object() { 
      @Override 
      public String toString() { 
       return String.format(s,o); 
      } 
     }; 
    } 
} 

kết quả đầu ra:

một số văn bản looong chuỗi với mẫu một chuỗi loooong

nhiên bạn có thể thêm bất kỳ câu lệnh isDebugEnabled() nào vào trong lazyFormat nếu bạn muốn.

+0

Phiên bản mới hơn của Log4J cho phép thay thế tham số xem http://stackoverflow.com/a/14078904/620113 – computermacgyver

+0

Điều này thực sự hoạt động - nó sẽ định dạng chuỗi nhưng nó có làm việc "lười biếng" không? Tôi đã thực hiện một thử nghiệm nhỏ, nơi arg của lazyFormat là các cuộc gọi đến một chức năng mà in để System.err và nó có vẻ như các args trong Object ... o được đánh giá ngay cả khi String.format (s, o) là không. – OneSolitaryNoob

+0

OneSolitaryNoob - Nó không làm điều lười biếng. Nhà cung cấp câu trả lời cho biết nó có thể được thực hiện bằng cách sử dụng phương thức "isDebugEnabled()" trong phương thức toString() methdod. Tôi đã cung cấp một mô hình đăng nhập lười biếng tổng quát hơn trong câu trả lời của tôi: http://stackoverflow.com/a/18317629/501113 – chaotic3quilibrium

13

nếu bạn đang tìm kiếm nối lười biếng vì lợi ích của khai thác gỗ hiệu quả, hãy nhìn vào Slf4J này cho phép bạn viết:

LOGGER.debug("this is my long string {}", fatObject); 

chuỗi nối sẽ chỉ diễn ra nếu mức độ debug được thiết lập .

+2

vấn đề đánh giá lười biếng chính xác là lý do tại sao cú pháp {} của slf4j được giới thiệu –

+0

Nếu không tốt, nhưng tôi bị kẹt với log4j. Tôi có loại thiết lập đăng nhập phức tạp, vì vậy tôi không thể chỉ cần thả slf4j vào. –

+2

SLF4J có một ràng buộc cho log4j. Vì vậy, bất cứ điều gì "thiết lập đăng nhập phức tạp" của bạn là, SLF4J sẽ xử lý nó độc đáo. Bạn có thể tiếp tục sử dụng log4j trực tiếp và SLF4J chỉ khi đánh giá chuỗi lười là cần thiết. – Ceki

0

Bạn có thể xác định trình bao bọc để chỉ gọi String.format() nếu cần.

Xem this question để biết ví dụ về mã chi tiết.

Câu hỏi tương tự cũng có một số variadic function example, như được đề xuất trong câu trả lời của Andreas.

3

xây dựng trên Andreas' answer, tôi có thể nghĩ đến một vài phương pháp tiếp cận đến vấn đề chỉ thực hiện định dạng nếu Logger.isDebugEnabled lợi nhuận true:

Lựa chọn 1: Vượt qua trong một lá cờ "làm định dạng"

Một tùy chọn là có đối số phương thức cho biết có thực sự thực hiện định dạng hay không.Một trường hợp sử dụng có thể là:

System.out.println(lazyFormat(true, "Hello, %s.", "Bob")); 
System.out.println(lazyFormat(false, "Hello, %s.", "Dave")); 

Trường hợp đầu ra sẽ là:

Hello, Bob. 
null 

Mã cho lazyFormat là:

private String lazyFormat(boolean format, final String s, final Object... o) { 
    if (format) { 
    return String.format(s, o); 
    } 
    else { 
    return null; 
    } 
} 

Trong trường hợp này, String.format chỉ thực hiện khi format cờ được đặt thành true và nếu được đặt thành false, thẻ này sẽ trả lại null. Điều này sẽ ngừng định dạng của thông báo ghi nhật ký xảy ra và sẽ chỉ gửi một số thông tin "giả".

Vì vậy, một trường hợp sử dụng với các logger có thể là:

logger.debug(lazyFormat(logger.isDebugEnabled(), "Message: %s", someValue)); 

Phương pháp này không chính xác phù hợp với những định dạng được yêu cầu trong câu hỏi.

Lựa chọn 2: Kiểm tra Logger

cách tiếp cận khác là yêu cầu logger trực tiếp nếu nó isDebugEnabled:

private static String lazyFormat(final String s, final Object... o) { 
    if (logger.isDebugEnabled()) { 
    return String.format(s, o); 
    } 
    else { 
    return null; 
    } 
} 

Trong phương pháp này, đó là mong rằng logger sẽ được hiển thị trong lazyFormat phương pháp. Và lợi ích của phương pháp này là người gọi sẽ không cần phải được kiểm tra phương pháp isDebugEnabled khi lazyFormat được gọi, vì vậy việc sử dụng điển hình có thể là:

logger.debug(lazyFormat("Debug message is %s", someMessage)); 
+0

Chẳng phải ví dụ cuối cùng là logger.debug (lazyFormat ("Debug message is% s", someMessage)); ? –

+0

@Juha S .: Vâng, bạn đã đúng. Tôi nhận thấy lỗi một chút sau khi tôi đăng câu trả lời, vì vậy nó đã được sửa. – coobird

3

Bạn có thể quấn dụ logger Log4J bên Java5- của riêng bạn tương thích/String.format lớp tương thích. Một cái gì đó như:

public class Log4jWrapper { 

    private final Logger inner; 

    private Log4jWrapper(Class<?> clazz) { 
     inner = Logger.getLogger(clazz); 
    } 

    public static Log4jWrapper getLogger(Class<?> clazz) { 
     return new Log4jWrapper(clazz); 
    } 

    public void trace(String format, Object... args) { 
     if(inner.isTraceEnabled()) { 
      inner.trace(String.format(format, args));  
     } 
    } 

    public void debug(String format, Object... args) { 
     if(inner.isDebugEnabled()) { 
      inner.debug(String.format(format, args));  
     } 
    } 

    public void warn(String format, Object... args) { 
     inner.warn(String.format(format, args));  
    } 

    public void error(String format, Object... args) { 
     inner.error(String.format(format, args));  
    } 

    public void fatal(String format, Object... args) { 
     inner.fatal(String.format(format, args));  
    }  
} 

Để sử dụng wrapper, thay đổi khai logger lĩnh vực của bạn để:

lớp
private final static Log4jWrapper logger = Log4jWrapper.getLogger(ClassUsingLogging.class); 

Các wrapper sẽ cần một vài phương thức bổ sung, ví dụ nó không hiện xử lý các trường hợp ngoại lệ khai thác gỗ (tức là logger.debug (tin nhắn, ngoại lệ)), nhưng điều này không khó để thêm vào.

Sử dụng lớp sẽ gần giống như log4j, ngoại trừ chuỗi được định dạng:

logger.debug("User {0} is not authorized to access function {1}", user, accessFunction) 
1

Hoặc bạn có thể viết nó như

debug(logger, "some texts %s with patterns %s", object1, object2); 

với

public static void debug(Logger logger, String format, Object... args) { 
    if(logger.isDebugEnabled()) 
     logger.debug(String.format("some texts %s with patterns %s", args)); 
} 
16

Nó có thể thực hiện bằng cách sử dụng thay thế tham số trong phiên bản mới nhất log4j 2.X http://logging.apache.org/log4j/2.x/log4j-users-guide.pdf:

4.1.1.2 Parameter Thay đổi

Mục đích của việc ghi nhật ký là cung cấp thông tin về những gì đang xảy ra trong hệ thống, trong đó yêu cầu bao gồm thông tin về các đối tượng đang được thao tác. Trong Log4j 1.x này có thể được thực hiện bằng cách thực hiện:

if (logger.isDebugEnabled()) {  
    logger.debug("Logging in user " + user.getName() + " with id " + user.getId()); 
} 

Việc làm này lặp đi lặp lại có tác dụng làm cho mã cảm thấy như nó là thêm về khai thác gỗ hơn nhiệm vụ thực tế trong tầm tay. Ngoài ra, kết quả ở mức ghi nhật ký được kiểm tra hai lần; một lần trên cuộc gọi đến làDebugEnabled và một lần trên phương thức gỡ lỗi. Một tốt hơn thay thế sẽ là:

logger.debug("Logging in user {} with id {}", user.getName(), user.getId()); 

Với mã trên mức khai thác gỗ sẽ chỉ được kiểm tra một lần và việc xây dựng chuỗi sẽ chỉ xảy ra khi debug logging được kích hoạt.

+1

Ví dụ của bạn có một chút sai lệch trong đó có rất ít sự khác biệt giữa ví dụ chưa được định dạng và định dạng của bạn; nghĩa là không có giá trị thực trong trình định dạng cho các hoạt động user.getName() hoặc user.getId() khi chúng được gọi ngay lập tức và các giá trị của chúng được truyền vào phương thức logger.debug. Các poster ban đầu đã đi qua hai trường hợp Object đếm trên thực tế là phương thức toString() trên những trường hợp đó sẽ không được gọi trừ khi chúng được cần đến. Tôi đã đăng câu trả lời chính xác hơn để ghi lại trạng thái "chỉ cuộc gọi cho dữ liệu nếu nó được sử dụng". – chaotic3quilibrium

+0

Log4j 2.4 [thêm hỗ trợ cho lambdas] (http://logging.apache.org/log4j/2.x/manual/api.html#LambdaSupport). –

5

LƯU Ý QUAN TRỌNG: Nó được khuyến khích mạnh mẽ tất cả các mã đăng nhập được chuyển sang sử dụng SLF4J (đặc biệt là log4j 1.x). Nó bảo vệ bạn khỏi bị mắc kẹt với bất kỳ loại vấn đề riêng biệt (tức là lỗi) với việc triển khai ghi nhật ký cụ thể. Nó không chỉ có "bản sửa lỗi" để biết rõ các vấn đề triển khai phụ trợ, nó cũng hoạt động với các triển khai nhanh hơn mới hơn đã xuất hiện trong những năm qua.


Trong phản ứng trực tiếp cho câu hỏi của bạn, ở đây những gì nó sẽ trông giống như sử dụng SLF4J:

LOGGER.debug("some texts {} with patterns {}", object1, object2); 

Các bit quan trọng nhất của những gì bạn đã cung cấp là một thực tế bạn đang đi qua hai trường hợp đối tượng. Các phương thức object1.toString()object2.toString() không được đánh giá ngay lập tức. Quan trọng hơn, các phương thức toString() chỉ được đánh giá nếu dữ liệu mà chúng trả về thực sự sẽ được sử dụng; tức là ý nghĩa thực sự của việc đánh giá lười biếng.

Tôi đã cố gắng nghĩ về một mẫu chung hơn tôi có thể sử dụng mà không yêu cầu phải ghi đè toString() trong tấn lớp (và có các lớp mà tôi không có quyền truy cập để ghi đè). Tôi đã đưa ra một giải pháp drop-in-place đơn giản. Một lần nữa, sử dụng SLF4J, tôi soạn chuỗi chỉ khi/khi đăng nhập cho cấp được kích hoạt. Dưới đây là mã của tôi:

class SimpleSfl4jLazyStringEvaluation { 
     private static final Logger LOGGER = LoggerFactory.getLogger(SimpleSfl4jLazyStringEvaluation.class); 

     ... 

     public void someCodeSomewhereInTheClass() { 
//all the code between here 
     LOGGER.debug(
      "{}" 
      , new Object() { 
       @Override 
       public String toString() { 
       return "someExpensiveInternalState=" + getSomeExpensiveInternalState(); 
       } 
      } 
//and here can be turned into a one liner 
     ); 
     } 

     private String getSomeExpensiveInternalState() { 
     //do expensive string generation/concatenation here 
     } 
    } 

Và để đơn giản hóa vào một liner, bạn có thể rút ngắn dòng Logger trong someCodeSomewhereInTheClass() là:

LOGGER.debug("{}", new Object(){@Override public String toString(){return "someExpensiveInternalState=" + getSomeExpensiveInternalState();}}); 

bây giờ tôi đã refactored tất cả các mã đăng nhập của tôi theo mô hình đơn giản này. Nó đã dọn dẹp mọi thứ một cách đáng kể. Và bây giờ khi tôi thấy bất kỳ mã đăng nhập nào không sử dụng mã này, tôi sẽ cấu trúc lại mã đăng nhập để sử dụng mẫu mới này ngay cả khi nó là cần thiết. Bằng cách đó, nếu/khi một thay đổi được thực hiện sau đó để cần thêm một số hoạt động "đắt tiền", thì bản đồ cơ sở hạ tầng đã có sẵn để đơn giản hóa nhiệm vụ chỉ cần thêm hoạt động.

2

Được giới thiệu trong Log4j 1.2.16 là hai lớp sẽ thực hiện việc này cho bạn.

org.apache.log4j.LogMF sử dụng java.text.MessageFormat để định dạng thư của bạn và org.apache.log4j.LogSF sử dụng "cú pháp mẫu SLF4J" và được cho là nhanh hơn.

Dưới đây là ví dụ:

LogSF.debug(log, "Processing request {}", req); 

LogMF.debug(logger, "The {0} jumped over the moon {1} times", "cow", 5); 
0

Nếu bạn thích String.format Cú pháp tốt hơn so với {0} Cú pháp và có thể sử dụng Java 8/JDK 8 bạn có thể sử dụng lambdas/Nhà cung cấp:

logger.log(Level.FINER,() -> String.format("SomeOperation %s took %04dms to complete", name, duration));

()->... hoạt động như một Nhà cung cấp ở đây và sẽ được đánh giá uể oải.

Các vấn đề liên quan