Using GGNN to recommend log statement level

In software engineering, log statement is an important part because programmers can't access to users' program and they can only rely on log message to find the root of bugs. The mechanism of"log level"allows developers and users to specify the appropriate amount of logs to print during the execution of the software. And 26\% of the log statement modification is to modify the level. We tried to use ML method to predict the suitable level of log statement. The specific model is GGNN(gated graph neural network) and we have drawn lessons from Microsoft's research. In this work, we apply Graph Neural Networks to predict the usage of log statement level of some open source java projects from github. Given the good performance of GGNN in this task, we are confident that GGNN is an excellent choice for processing source code. We envision this model can play an important role in applying AI/ML technique for Software Development Life Cycle more broadly.


Introduction
When a system breaks down, developers usually can't access to it because the program runs on user's computer. The only thing on which they can rely is the log statement. A log statement can report where the error takes place and the value of some critical variables so that developers can give a diagnosis. Ideally, coders can put log statements after every non-log statement. Theoretically speaking, doing like this eliminates ambiguity to the greatest extent. Unfortunately, it will introduce unbearable performance overhead. So we have to consider where to place a log statement is a best choice. Currently, there are some tools which can help programmers to make a decision whether to write a log statement. For example, Log20 can automates log statement placement without any domain knowledge. [5] But it's not enough to just locate log statements. In engineering, an experienced coder is often aware of which lines are turning points and that he should place a log statement there. The question is how to make a trade off between system overhead and program running information. Using log verbosity level is a solution. Common log libraries like Log4j and SLF4J support six log levels, including trace, debug, info, warn, error and fatal. Users can control the verbosity level of log statements to arXiv:1912.05097v1 [cs.SE] 11 Dec 2019 be printed out during execution. [1] By doing this, debuggers can adjust the amount of log information that they receive dynamically. However, assigning the right verbosity level requires sufficient experience. Developers spend significant efforts on adjusting the verbosity level of log messages, accounting for 26% of all the log improvements. Majority (72%) of them reflect the changes in developers' judgment about the criticality of an error event. [2] We build a machine learning model based on GGNN to help developers to choose the right verbosity level.
When it comes to using machine learning to solve problems in software engineering, most of the state-of-art projects are concentrated on natural language processing (NLP) method. Indeed, source code and natural language are similar in many ways, such as they are both made up by limited characters and symbols and are both sequential. But we still can't treat them equally. As the size of vocabulary of the source code is beyond imagination while the same of natural language is limited, we can find that most of the words in the code are created temporarily, and they are not used a lot. That means it is tough to learn the semantic information of these words. For example, the number of total tokens of WMT-14 English dataset is 304,000,000 and the size of vocabulary of it is 160,000, while there are 12,095,591 tokens in Eclipse 3.5.2 and its vocabulary size is 291,671. [6] All of these leads to difficulties in applying NLP to source code. The paradox is that some simple non-deep-learning method even outperforms complicated deep learning model in this field. [7] We assume the reason why some deep learning models don't perform well is that they can't take structural information into account and most identifiers are unknown for them so that they can't extract enough semantic information. Maybe we should change our perspective. Code is more like being organized as a graph. Whenever there is a function call, a variable definition or an operational expressions, an edge will be linked. So graph neural networks (GNN) is also a possibility because it can describe the structure information of the program by setting up some edges. GGNN is a kind of GNN. It realizes the information transfer between nodes through GRU. We use GGNN to learn the representation vector of a log statement and get a log level prediction by analysing the vector with a MLP.
We make the following contributions: 1)We build a model based on GGNN to predict log level. It can recommend level of a log statement according to the program subgraph whose center is the log statement. 2)We evaluate this model on 29 open-source java projects from Apache on Github and compare it with random model , LSTM model and Heng Li's model. [1] The result shows that this model outperforms them.

Related Work
Our work is based on prior researches on log sentences and log levels.  [8] presented log20 to determine a near optimal placement of log printing statements. Yuan et al.(2010) [9] analyzes source code by leveraging information provided by run-time logs to infer what must or may have happened during the failed production run. Zhu et al.(2015) [10] proposed LogAdvisor that extracts contextual features and helps developers to determine where to log. This model is constructed on recent works of representing source code in machine learning models. Alon et al.(2018a) [11], Alon et al.(2018b) [12] represent code snippets as a set of compositional paths over its abstract syntax tree (AST) and use LSTM to compress the paths into fixed-length vector. Allamanis et al.(2014) [13] introduce NATURALIZE to learn local context and solve coding convention inference problem. Vasic et al.(2019) [14] construct multi-head pointer network to perform joint prediction of both the location and the repair for variable misuse bugs. Our work is closely related to the research of Heng Li et al.(2017) [1] who analyze the key features of log level through case study and represented the features with arrays. However, they manually extract the features and do not use the semantic information of the code. Graph neural network is used to extract the syntactic and semantic features of source code in this model. Gori et al.(2005) [15] proposed GNN to cope with graphical data structures and Li et al.

Log Verbosity Level Prediction Task
Whatever programming language you use, printing log information is necessary. The main purposes of printing log information are as follows: 1.We can find bugs in programs through it. 2.We can monitor the status of the system and prevent problems from happening through it. 3.We can ferret out unsafe, unauthorized operations to guard against attacks from others. Although there are so many benefits, the best log library still have impact on performance. To minimize the overhead, one solution is to assign different levels to different log statements and print them by level in appropriate situations. Generally speaking, the log level can be divided into six categories: trace, debug, info, warn, error and fatal. Trace-level statements are the most detailed and mainly used in the development phase. Once being deployed to production environments, they should be deleted. Debug-level statements are similar to trace-level statements. The difference is that the former lasts longer. Info-level statements are used to print some normal information. Warn-level statements mean that there are some potential problems, but the program can go on. Error-level statements appear when some very serious problems occur. These problems will cause the current program to fail to execute properly. But if an exception handling mechanism exists, the system will not crash. Finally, fatal-level statements indicate that the worst case occurs, that is, the system crashes. Some of these levels are easily confused, such as trace and debug, warn and error. Choosing the right log level consumes a lot of energy for programmers, 26% of all the log improvements are targeted at adjusting the log level. [2] There is a correlation between the log statements and their program contexts, because they need to appear in the event of problems and print some related variables if necessary. If they don't appear in particular locations, the values of these variables may change and become meaningless. So it's possible to judge a log statement level from its context. Our goal is to build a model for predicting log levels based on program context.

Model
This model is based on GGNN [3], and the definitions of node and edge are inherited from a related Microsofts' research. [4]Considering the characteristics of this task, we use MLP to handle the output vector of GNN.

Graph Neural Networks
The first part of this model is GNN and we summarize it here. A graph G = (N , E , X ) has three parts: a set of nodes, a list of edges and initial representations of nodes, and there can be several kinds of edges. Edges are pairs e = (v 1 , v 2 ) ∈ N × N . The initial representations of nodes are generally a list of real vector x. The purpose of GNN is to learn node vector which is donated by h. Vector x n and vector h n have different meanings, the former is our prior knowledge about a node while the latter is the output of GNN with graph message.
Generally, GNN works like this: firstly there are several iterations of propagation that compute every node representation vector h; secondly there is a output function g(h, x) that calculate the output that we want to get. In order to get a prediction of log level, every node from which we want to get output is a log statement, and their initial representations are the same, so the output function can be simplified to g(h). The parameters of output function g(h) can be learned and we use MLP as function g(h).
In this section, we will elaborate on the propagation step. At the first, we assign the value of every initial node vector x n to v n0 . Generally, the sizes of x and v are the same. However we can enlarge the size of vector v by padding zeros at the end of it. In each iteration process, every node sends message to its neighbors. The content of messages is determined by both node vectors and the type of edges: m kn means the message which node n sends through edge of type k. And the parameters of function f k can be learned. Referring to the method in LEARNING TO REPRESENT PROGRAMS WITH GRAPHS [4], we set this function to be linear. For those nodes which have plural neighbors, a process of aggregating messages is necessary before updating. There are two simple methods to aggregate messages: maximizing or averaging. In each iteration, every node vector v is updated at the same time. The update operation is as follows: once every node gets its aggregated messagẽ m, the node vector of next step is calculated by: GRU is the recurrent cell function of gated recurrent unit. [18] After a certain number of iterations, the final node vector contains not only the information of the node itself, but also the information of a subgraph whose center is the node.

Program Graphs
In order to apply GGNN to processing code, we have to convert source code to graph. In fact, source code is inherently graphical. We use the definitions of nodes and edges in LEARNING TO REPRESENT PROGRAMS WITH GRAPHS [4] and we will elaborate on them in this section. node type: There are 7 kinds of node, and their definition is in Table 1. A COMPUTED FROM edge links a node to another node from which it is computed. Here is an instance in which there is a COMPUTED FROM edge between "x" and "y": 1 x = y + 5 ; A RETURNS TO edge links "return" token to the method declaration. A FORMAL ARG NAME edge links arguments in the method calls to the formal parameters that they are matched to. [19] Here is an instance in which there is a COMPUTED FROM edge between "param" and "x": GUARDED BY and GUARDED BY NEGATION edges are used to connect every token corresponding to a variable to enclosing guard expressions. Here is an instance in which there is a GUARDED BY edge between "x" and "x>y" and a GUARDED BY NEGATION edge between "y" and "x>y", "x>y" is an AST node:

MLP
MLP is the simplest neural network. The output of GGNN is supposed to be a real vector and it is suitable for processing with MLP. Now, our task is to divide the vector into six categories. So we construct a neural network consisting of several full-connected layers to extract the features of vectors automatically. The output of it is a vector of length 6. The value of the n-th component of this vector represents the possibility that the log statement is of level n. Figure 2 shows the overall framework of the model.

preprocessing method
The goal of preprocessing is to convert java source code to a number of labeled log statement-centric subgraph. We use an open-source java tool, features-javac, to accomplish the first step. 1 It not only converts source code to graph but also connects all defined edges. The graph are saved as proto format and then parsed with Google's protobuf library. 2 Sometimes log statements are hard to figure out. Different systems usually have log statements in different formats. In order to locate log statement and ascertain its verbosity level, we have to ensure that the program uses common log library. There are several common log library, e.g. Log4j 3 and slf4j 4 . Using these libraries, log statements are like the following: It's easy to determine whether a line of code is a log statement by identifying whether there is a string "log" and a verbosity level keyword in it. A simple script can accomplish this task.

subgraph extraction
The purpose of this model is to predict the verbosity level of a log statement according to a graph containing it. But we have to get rid of the log statement' own information. In order to ensure that the processed program can still be compiled so that graphs can be generated, we simply replace the log statement with a semicon. For example, the code block above will become like this after processing: Considering that the number of steps of propagation is limited, a subgraph already meets our needs. Then what we should do is to generate a subgraph whose center is the semicon. Specific approach is to add all points within several steps from the semicon to the node set of the subgraph and then add all edges whose starting node and ending node are in the node set to the edge set of the subgraph. In fact, selection of subgraph is pretty flexible. More steps will introduce more information and the model may learn more complex features while the problem of data sparsity will arise. Less steps helps to learn some direct or superficial features. Selecting nodes within m steps and outside n steps can help to exclude some unessential features.

token embedding
A universal problem in applying NLP or other ML methods to code is countless kinds of tokens. An important reason of this problem is that programmers prefer to combine several words into an identifier token. Generally, they do this with UnderScoreCase or camelCase. Once we split every identifier into several parts, the vocabulary is reduced to an acceptable level. Inspired by Deep-AutoCoder [6], we can further reduce the vocabulary by ignoring words that occur less than a certain number of times and replacing them with "UNKNOWN".
After reducing the vocabulary, we can directly make token embedding, and the parameters of the embedding layer are updated during training. For tokens which can be split into several words, we average the embeddings of these words as their embeddings. Finally we get embeddings of all nodes and use them as the initial node representations.

hyper-parameters and other configures
We use SparseGGNN in Microsoft's dpu_util library. 5 The size of embedding vector is 64. We use GRU as the rnn cell of the network. The activation function of gru is RELU. The number of propagation steps is 8. There are four layers in the succeeding MLP. Their sizes are 64, 32, 16, 6. The activation function of the first three layer is RELU.

dataset
We collected a dataset from 29 top-stared open-source java projects from Apache on Github. Then we filtered out the files with log sentences. After preprocessing, we got 8,000 labeled log samples. We selected the samples in 25 projects as seen data and divided them into train set, validation set and test set in the proportion 7-1-2 and use the samples in the other 4 projects as unseen data to examine the generalization capability of this model.

evaluation metrics
We use AUC and accuracy to evaluate this model. AUC is the area under ROC curve, and it is used to evaluate the degree of discrimination that is achieved by a model [1]. Higher AUC indicates better performance on discrimination of a model.

baseline
We compare this model with the performance of random-guessing model, RNN model and Heng Li's model [1]. The random guessing model randomly assign labels to samples and doesn't learn from the train set. The RNN(recurrent neural network) model is commonly used to process serial inputs. It takes all the sentences in the code as input and learn the structural and semantic information of the code. Heng Li's team analyzed the structural information of log sentences, extracted the key features of log level from case study and used ordinal regression to determine which log level should be chosen. Table 1 shows the performance of this model comparing to the baseline models.   Figure 3 shows the confusion matrix of this model. The frequency of fatal in the 25 projects is less than 0.1% so we omitted the label fatal in the matrix. As is shown in the graph, the correct log level account for the highest proportion for all true log levels except trace, because the use of trace varies in different projects. Even if a log sentence is assigned to the wrong log levels, they are most likely to be assigned to the log levels adjacent to their true log levels, which means the errors tend to be acceptable.

quantitative evaluation
The code blocks below show examples of determining log levels. The location of the log statements is connected to the statement 'throw' in the next line through NextToken edge, suggesting a high log level. Therefore, it is predicted as log level 'error' with high confidence and the prediction array is From the statement 'LOG.isDebugEnabled()', the model knows the log statement is of low log level, but due to lack of information in the block, it could not determine the precise log level and gave a high degree of confidence to both 'debug' and 'info', and it is likely for the model to assign a slightly wrong log level to the statement and the prediction array is [0.025, 0.369, 0.595, 0.0003, 0.011, 1.9e-6] for log levels [trace, debug, info, warn, error, fatal], indicating high possibility for both debug and info.