简体   繁体   English

Java PrintWriter错误

[英]Java PrintWriter Error

I'm a long time reader, but first time writer. 我是一位长期读者,但是第一次写作。

I am currently trying to implement a logger with AspectJ in our codebase. 我目前正在尝试在代码库中使用AspectJ实现一个记录器。 AspectJ seems to work well, but I am encountering extremely weird Java errors. AspectJ似乎运行良好,但我遇到了非常奇怪的Java错误。 I am a longtime C++ and .Net developer who is still adapting to the world of Java, so I apologize if this is a dumb question. 我是一个长期的C ++和.Net开发人员,他仍然适应Java的世界,所以如果这是一个愚蠢的问题,我道歉。

My code is trying to trap exceptions, and log the pertinent info to a text file. 我的代码试图捕获异常,并将相关信息记录到文本文件中。 The trapping works fine, but I noticed that when I deployed, I am not getting any data. 陷阱工作正常,但我注意到,当我部署时,我没有得到任何数据。 I opened my class file in a Java decompiler, and noticed that the PrintWriter seems to be generating an error. 我在Java反编译器中打开了我的类文件,并注意到PrintWriter似乎正在生成错误。 I have never seen a problem like this, so I am hoping one you might have any insight. 我从来没有见过像这样的问题,所以我希望你可以有任何见解。

package mil.uscg.c3cen.vic.aspect;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.JoinPoint;

@Aspect
public class LoggingAspect
{
    private final String LOG_FILE = "aspectLog.txt";
    private final File file = new File(LOG_FILE);

    private LoggingAspect() 
    { 

    }

    private void logException(String msg) 
    {
        try 
        {
            if(!file.exists())
                file.createNewFile();
        }
        catch(IOException e) 
        {

        }

        try (FileWriter fw = new FileWriter(file); 
               BufferedWriter bw = new BufferedWriter(fw);
               PrintWriter pw = new PrintWriter(bw)) 
        {
            pw.println(msg);
        }
        catch(IOException e)
        {

        }
    }

    private String getSimpleFunctionInfo(String className, String     function, Object[] args)
    {
        StringBuilder builder = new StringBuilder();
        builder.append(". Method: ");
        builder.append(className);
        builder.append(".");
        builder.append(function);

        if(args.length == 0)
        {
            builder.append("()");
            return builder.toString();
        }

        builder.append("(");

        for(Object o : args)
        {
            builder.append(o.toString());
            builder.append(",");
        }
        // Replace the comma for the last param with a closing parenthesis
        int len = builder.length();
        builder.replace(len -1, len, ")");

        return builder.toString();
    }

    // Returns a formatted exception. "Exception.ErrorMessage"
    private String getSimpleExceptionInfo(String name, String msg)
    {
        StringBuilder builder = new StringBuilder();
        builder.append("Exception caught: ");
        builder.append(name);
        builder.append(". Message: ");
        builder.append(msg);
        return builder.toString();
    }


   @AfterThrowing(pointcut = "execution(* mil.uscg.c3cen.*.*.*(..)) "
       //+ "&& !within(mil.uscg.c3cen.vic.aspect.*) "
       , throwing = "excep")
    public void afterThrowing(JoinPoint jp, Throwable excep) throws Throwable
    {
        String ex = getSimpleExceptionInfo(excep.getClass().getSimpleName(), 
                                                             excep.getMessage());

        String name = getSimpleFunctionInfo(jp.getSignature().getDeclaringType().getSimpleName(), 
                                                               jp.getSignature().getName(), 
                                                               jp.getArgs());

        StringBuilder builder = new StringBuilder();
        builder.append(ex);
        builder.append(name);

        logException(builder.toString());
    } 
}

Everything looks as you would expect in the class file, except for the function logException. 除了函数logException之外,一切看起来都像在类文件中一样。

/* Error */
  private void logException(String msg)
  {
    // Byte code:
    //   0: aload_0
    //   1: getfield 25 mil/uscg/c3cen/vic/aspect/LoggingAspect:file    Ljava/io/File;
    //   4: invokevirtual 32    java/io/File:exists ()Z
    //   7: ifne +15 -> 22
    //   10: aload_0
    //   11: getfield 25    mil/uscg/c3cen/vic/aspect/LoggingAspect:file    Ljava/io/File;
    //   14: invokevirtual 36   java/io/File:createNewFile  ()Z
    //   17: pop
    //   18: goto +4 -> 22
    //   21: pop
    //   22: aconst_null
    //   23: astore_2
    //   24: aconst_null
    //   25: astore_3
    //   26: new 39 java/io/FileWriter
    //   29: dup
    //   30: aload_0
    //   31: getfield 25    mil/uscg/c3cen/vic/aspect/LoggingAspect:file    Ljava/io/File;
    //   34: invokespecial 41   java/io/FileWriter:<init>   (Ljava/io/File;)V
    //   37: astore 4
    //   39: new 44 java/io/BufferedWriter
    //   42: dup
    //   43: aload 4
    //   45: invokespecial 46   java/io/BufferedWriter:<init>   (Ljava/io/Writer;)V
    //   48: astore 5
    //   50: new 49 java/io/PrintWriter
    //   53: dup
    //   54: aload 5
    //   56: invokespecial 51   java/io/PrintWriter:<init>  (Ljava/io/Writer;)V
    //   59: astore 6
    //   61: aload 6
    //   63: aload_1
    //   64: invokevirtual 52   java/io/PrintWriter:println (Ljava/lang/String;)V
    //   67: aload 6
    //   69: ifnull +24 -> 93
    //   72: aload 6
    //   74: invokevirtual 55   java/io/PrintWriter:close   ()V
    //   77: goto +16 -> 93
    //   80: astore_2
    //   81: aload 6
    //   83: ifnull +8 -> 91
    //   86: aload 6
    //   88: invokevirtual 55   java/io/PrintWriter:close   ()V
    //   91: aload_2
    //   92: athrow
    //   93: aload 5
    //   95: ifnull +43 -> 138
    //   98: aload 5
    //   100: invokevirtual 58  java/io/BufferedWriter:close    ()V
    //   103: goto +35 -> 138
    //   106: astore_3
    //   107: aload_2
    //   108: ifnonnull +8 -> 116
    //   111: aload_3
    //   112: astore_2
    //   113: goto +13 -> 126
    //   116: aload_2
    //   117: aload_3
    //   118: if_acmpeq +8 -> 126
    //   121: aload_2
    //   122: aload_3
    //   123: invokevirtual 59  java/lang/Throwable:addSuppressed   (Ljava/lang/Throwable;)V
    //   126: aload 5
    //   128: ifnull +8 -> 136
    //   131: aload 5
    //   133: invokevirtual 58  java/io/BufferedWriter:close    ()V
    //   136: aload_2
    //   137: athrow
    //   138: aload 4
    //   140: ifnull +66 -> 206
    //   143: aload 4
    //   145: invokevirtual 65  java/io/FileWriter:close    ()V
    //   148: goto +58 -> 206
    //   151: astore_3
    //   152: aload_2
    //   153: ifnonnull +8 -> 161
    //   156: aload_3
    //   157: astore_2
    //   158: goto +13 -> 171
    //   161: aload_2
    //   162: aload_3
    //   163: if_acmpeq +8 -> 171
    //   166: aload_2
    //   167: aload_3
    //   168: invokevirtual 59  java/lang/Throwable:addSuppressed   (Ljava/lang/Throwable;)V
    //   171: aload 4
    //   173: ifnull +8 -> 181
    //   176: aload 4
    //   178: invokevirtual 65  java/io/FileWriter:close    ()V
    //   181: aload_2
    //   182: athrow
    //   183: astore_3
    //   184: aload_2
    //   185: ifnonnull +8 -> 193
    //   188: aload_3
    //   189: astore_2
    //   190: goto +13 -> 203
    //   193: aload_2
    //   194: aload_3
    //   195: if_acmpeq +8 -> 203
    //   198: aload_2
    //   199: aload_3
    //   200: invokevirtual 59  java/lang/Throwable:addSuppressed   (Ljava/lang/Throwable;)V
    //   203: aload_2
    //   204: athrow
    //   205: pop
    //   206: return
    // Line number table:
    //   Java source line #28   -> byte code offset #0
    //   Java source line #29   -> byte code offset #10
    //   Java source line #30   -> byte code offset #18
    //   Java source line #31   -> byte code offset #21
    //   Java source line #36   -> byte code offset #22
    //   Java source line #36   -> byte code offset #26
    //   Java source line #37   -> byte code offset #39
    //   Java source line #38   -> byte code offset #50
    //   Java source line #40   -> byte code offset #61
    //   Java source line #41   -> byte code offset #67
    //   Java source line #42   -> byte code offset #205
    //   Java source line #46   -> byte code offset #206
    // Local variable table:
    //   start  length  slot    name    signature
    //   0  207 0   this    LoggingAspect
    //   0  207 1   msg String
    //   23 1   2   localObject1    Object
    //   80 28  2   localObject2    Object
    //   112    92  2   localObject3    Object
    //   25 1   3   localObject4    Object
    //   106    17  3   localThrowable1 Throwable
    //   151    17  3   localThrowable2 Throwable
    //   183    17  3   localThrowable3 Throwable
    //   37 140 4   fw  java.io.FileWriter
    //   48 84  5   bw  java.io.BufferedWriter
    //   59 28  6   pw  java.io.PrintWriter
    //   21 1   12  localIOException1   java.io.IOException
    //   205    1   13  localIOException2   java.io.IOException
    // Exception table:
    //   from   to  target  type
    //   0  18  21  java/io/IOException
    //   61 67  80  finally
    //   50 93  106 finally
    //   39 138 151 finally
    //   26 183 183 finally
    //   22 205 205 java/io/IOException
  }

This has really stumped me, so any information will be extremely appreciated. 这真的让我难过,所以任何信息都会非常感激。 Thanks! 谢谢!

Okay, I tried with Java 8 and current AspectJ 1.8.8. 好的,我尝试使用Java 8和当前的AspectJ 1.8.8。 Your aspect works as expected (I compiled it without any changes). 您的方面按预期工作(我编译它没有任何更改)。 It is just a bit over-complicated and should be simplified. 它有点过于复杂,应该简化。 Furthermore, you might just have mis-counted the number of .* in your pointcut. 此外,您可能只是错误地计算了切入点中的.*的数量。

If I add System.out.println(jp); 如果我添加System.out.println(jp); at the beginning of your advice method so as to see something on the console and run your aspect against this sample driver class ... 在您的建议方法的开头,以便在控制台上查看某些内容并针对此示例驱动程序类运行您的方面...

package mil.uscg.c3cen.foo;

public class Application {
    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            try {
                doSomething();
            }
            catch (Exception e) {}
        }
    }

    public static void doSomething() {
        System.out.println("Calculation result = " + multiply(add(3, 4), 5));
        System.out.println("Calculation result = " + divide(add(5, 6), 0));
    }

    private static int add(int summand1, int summand2) {
        return summand1 + summand2;
    }

    private static int multiply(int factor1, int factor2) {
        return factor1 * factor2;
    }

    private static int divide(int dividend, int divisor) {
        return dividend / divisor;
    }
}

... the console log looks like this: ...控制台日志如下所示:

Calculation result = 35
execution(int mil.uscg.c3cen.foo.Application.divide(int, int))
execution(void mil.uscg.c3cen.foo.Application.doSomething())
Calculation result = 35
execution(int mil.uscg.c3cen.foo.Application.divide(int, int))
execution(void mil.uscg.c3cen.foo.Application.doSomething())
Calculation result = 35
execution(int mil.uscg.c3cen.foo.Application.divide(int, int))
execution(void mil.uscg.c3cen.foo.Application.doSomething())

As you can see, only the methods throwing the exception up the call hierarchy (until they are caught) are logged, as expected. 正如您所看到的,只有按预期方式记录抛出调用层次结构中的异常(直到它们被捕获)的方法。 The log file aspectLog.txt has this content: 日志文件aspectLog.txt具有以下内容:

Exception caught: ArithmeticException. Message: / by zero. Method: Application.main([Ljava.lang.String;@f6f4d33)

What to improve: 要改进的地方:

  • Maybe you want to have a more stable pointcut targetting all subpackages of mil.uscg.c3cen . 也许你想要一个更稳定的切入点,目标是mil.uscg.c3cen所有子包。 The syntax for "all method executions within that package and all its subpackages" would be execution(* mil.uscg.c3cen..*(..)) . “该包及其所有子包中的所有方法执行”的语法将是execution(* mil.uscg.c3cen..*(..))
  • There is a bug in your log file usage logic: Whenever the very first exception occurs and the log file does not exist yet, it gets logged and the log file immediately closed. 日志文件使用逻辑中存在一个错误:每当第一个异常发生且日志文件尚不存在时,它就会被记录并立即关闭日志文件。 Any subsequent exception will never be logged, which is probably not what you want. 永远不会记录任何后续异常,这可能不是您想要的。 You probably want multiple exceptions logged into the same file as long as the JVM is up and running. 只要JVM启动并运行,您可能希望将多个异常记录到同一文件中。 Thus, you do not want to close the log file after each write but take care of that in a JVM shutdown hook at the end of the JVM lifetime. 因此,您不希望在每次写入后关闭日志文件,而是在JVM生命周期结束时在JVM关闭挂钩中处理它。 Try with (auto-closeable) resources is only useful if you really want to close them after running a certain part of the code. 尝试使用(自动关闭)资源仅在您确实要在运行代码的某个部分后关闭它们时才有用。 BTW, you can avoid the shutdown hook by flushing the writer on a regular basis, eg after each println call. 顺便说一句,您可以通过定期刷新写入程序来避免关闭挂钩,例如在每次println调用之后。
  • I am not discussing threading and synchronisation issues here, that would require even more care. 我不是在讨论线程和同步问题,这需要更多关注。 Let us assume you have a single-threaded application. 我们假设您有一个单线程应用程序。
  • Maybe you also want to intercept exceptions in constructors and add that to your pointcut. 也许你还想拦截构造函数中的异常并将其添加到你的切入点。

Improved & simplified aspect: 改进和简化方面:

package mil.uscg.c3cen.vic.aspect;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class LoggingAspect {
    private static final String LOG_FILE = "aspectLog.txt";

    private final PrintWriter logWriter; 

    public LoggingAspect() throws FileNotFoundException {
        logWriter = new PrintWriter(new FileOutputStream(LOG_FILE));
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                logWriter.close();
            }
        });
    }

    @AfterThrowing(
        pointcut =
            "(execution(* mil.uscg.c3cen..*(..)) || execution(mil.uscg.c3cen..new(..)))" +
            " && !within(mil.uscg.c3cen.vic.aspect..*) ",
        throwing = "excep"
    )
    public void afterThrowing(JoinPoint jp, Throwable excep) throws Throwable {
        //System.out.println(excep + " -> " + jp);
        logWriter.println(excep + " -> " + jp);
    }
}

Extended code sample with a constructor throwing an exception: 带有构造函数抛出异常的扩展代码示例:

package mil.uscg.c3cen.foo;

public class Application {
    public Application() {
        System.out.println(1/0);
    }

    public static void doSomething() {
        System.out.println("Calculation result = " + multiply(add(3, 4), 5));
        System.out.println("Calculation result = " + divide(add(5, 6), 0));
    }

    private static int add(int summand1, int summand2) {
        return summand1 + summand2;
    }

    private static int multiply(int factor1, int factor2) {
        return factor1 * factor2;
    }

    private static int divide(int dividend, int divisor) {
        return dividend / divisor;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            try {
                doSomething();
            }
            catch (Exception e) {}
        }
        try {
            new Application();
        }
        catch (Exception e) {}
    }
}

Console log: 控制台日志:

Calculation result = 35
Calculation result = 35
Calculation result = 35

Log file: 日志文件:

java.lang.ArithmeticException: / by zero -> execution(int mil.uscg.c3cen.foo.Application.divide(int, int))
java.lang.ArithmeticException: / by zero -> execution(void mil.uscg.c3cen.foo.Application.doSomething())
java.lang.ArithmeticException: / by zero -> execution(int mil.uscg.c3cen.foo.Application.divide(int, int))
java.lang.ArithmeticException: / by zero -> execution(void mil.uscg.c3cen.foo.Application.doSomething())
java.lang.ArithmeticException: / by zero -> execution(int mil.uscg.c3cen.foo.Application.divide(int, int))
java.lang.ArithmeticException: / by zero -> execution(void mil.uscg.c3cen.foo.Application.doSomething())
java.lang.ArithmeticException: / by zero -> execution(mil.uscg.c3cen.foo.Application())

Look at the last line, there you see the exception in the constructor. 查看最后一行,在构造函数中看到异常。

If you want to beautify the exception logging output a bit, similar to what your original aspect does, do this: 如果您想要美化异常日志记录输出,类似于原始方面的操作,请执行以下操作:

logWriter.println(excep.getClass().getSimpleName() + " -> " + jp.getSignature());

Then the log file becomes: 然后日志文件变为:

ArithmeticException -> int mil.uscg.c3cen.foo.Application.divide(int, int)
ArithmeticException -> void mil.uscg.c3cen.foo.Application.doSomething()
ArithmeticException -> int mil.uscg.c3cen.foo.Application.divide(int, int)
ArithmeticException -> void mil.uscg.c3cen.foo.Application.doSomething()
ArithmeticException -> int mil.uscg.c3cen.foo.Application.divide(int, int)
ArithmeticException -> void mil.uscg.c3cen.foo.Application.doSomething()
ArithmeticException -> mil.uscg.c3cen.foo.Application()

Well, you didn't really tell what the issue is. 好吧,你并没有真正说出问题所在。 If you have problem with uncaught exception in logException then just catch it and investigate. 如果你在logException中遇到未捕获异常的问题,那么就抓住它并进行调查。 Most probably it's permission issue when opening the file or maybe (on windows for example) the OS doesn't allow access to the file by several threads. 很可能是打开文件时的权限问题,或者可能(例如在Windows上)操作系统不允许多个线程访问该文件。

For the second question - byte code is quit clear. 对于第二个问题 - 字节代码退出清除。 First part is quite straightforward and follows the code you wrote. 第一部分非常简单,并遵循您编写的代码。 The second part handles closing the resources and adding potentially caught exceptions as suppressed ones to already raised exception. 第二部分处理关闭资源并将可能捕获的异常作为已抑制的异常添加到已经引发的异常。 It's a bit long but again follows the resource initialization in opposite direction. 它有点长但是在相反方向上的资源初始化之后。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM