1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.net.util;
18
19import android.text.TextUtils;
20import android.util.LocalLog;
21import android.util.Log;
22
23import java.io.FileDescriptor;
24import java.io.PrintWriter;
25import java.util.StringJoiner;
26
27
28/**
29 * Class to centralize logging functionality for tethering.
30 *
31 * All access to class methods other than dump() must be on the same thread.
32 *
33 * @hide
34 */
35public class SharedLog {
36    private final static int DEFAULT_MAX_RECORDS = 500;
37    private final static String COMPONENT_DELIMITER = ".";
38
39    private enum Category {
40        NONE,
41        ERROR,
42        MARK,
43        WARN,
44    };
45
46    private final LocalLog mLocalLog;
47    // The tag to use for output to the system log. This is not output to the
48    // LocalLog because that would be redundant.
49    private final String mTag;
50    // The component (or subcomponent) of a system that is sharing this log.
51    // This can grow in depth if components call forSubComponent() to obtain
52    // their SharedLog instance. The tag is not included in the component for
53    // brevity.
54    private final String mComponent;
55
56    public SharedLog(String tag) {
57        this(DEFAULT_MAX_RECORDS, tag);
58    }
59
60    public SharedLog(int maxRecords, String tag) {
61        this(new LocalLog(maxRecords), tag, tag);
62    }
63
64    private SharedLog(LocalLog localLog, String tag, String component) {
65        mLocalLog = localLog;
66        mTag = tag;
67        mComponent = component;
68    }
69
70    public SharedLog forSubComponent(String component) {
71        if (!isRootLogInstance()) {
72            component = mComponent + COMPONENT_DELIMITER + component;
73        }
74        return new SharedLog(mLocalLog, mTag, component);
75    }
76
77    public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
78        mLocalLog.readOnlyLocalLog().dump(fd, writer, args);
79    }
80
81    //////
82    // Methods that both log an entry and emit it to the system log.
83    //////
84
85    public void e(Exception e) {
86        Log.e(mTag, record(Category.ERROR, e.toString()));
87    }
88
89    public void e(String msg) {
90        Log.e(mTag, record(Category.ERROR, msg));
91    }
92
93    public void i(String msg) {
94        Log.i(mTag, record(Category.NONE, msg));
95    }
96
97    public void w(String msg) {
98        Log.w(mTag, record(Category.WARN, msg));
99    }
100
101    //////
102    // Methods that only log an entry (and do NOT emit to the system log).
103    //////
104
105    public void log(String msg) {
106        record(Category.NONE, msg);
107    }
108
109    public void mark(String msg) {
110        record(Category.MARK, msg);
111    }
112
113    private String record(Category category, String msg) {
114        final String entry = logLine(category, msg);
115        mLocalLog.log(entry);
116        return entry;
117    }
118
119    private String logLine(Category category, String msg) {
120        final StringJoiner sj = new StringJoiner(" ");
121        if (!isRootLogInstance()) sj.add("[" + mComponent + "]");
122        if (category != Category.NONE) sj.add(category.toString());
123        return sj.add(msg).toString();
124    }
125
126    // Check whether this SharedLog instance is nominally the top level in
127    // a potential hierarchy of shared logs (the root of a tree),
128    // or is a subcomponent within the hierarchy.
129    private boolean isRootLogInstance() {
130        return TextUtils.isEmpty(mComponent) || mComponent.equals(mTag);
131    }
132}
133