各种语言的宏技巧 日志追踪

C / C++

https://www.cnblogs.com/develon/p/7845880.html

日志追踪

安卓NDK

编译器内置 __FILE_NAME__, __LINE__,__func__,__FUNCTION__ 等宏,可追踪打印日志的代码。

#include <android/log.h>
#define R(x) #x
#define STR(x) R(x)
#define LOG(...) __android_log_print(ANDROID_LOG_DEBUG, __FILE_NAME__ ":" STR(__LINE__), ##__VA_ARGS__)
#define TLOG(tag, fmt, ...) __android_log_print(ANDROID_LOG_DEBUG, __FILE_NAME__ ":" STR(__LINE__), "%s -> " fmt, tag, ##__VA_ARGS__)
#define FLOG(fmt, ...) TLOG(__func__, fmt, ##__VA_ARGS__)


LOG("The value of 1 + 1 is %d", 1+1);
输出:

2023-05-27 19:03:22.609 12721-12721/app.xxx D/native-lib.cpp:27: The value of 1 + 1 is 2

MSVC

#define R(x) #x
#define STR(x) R(x)
/**
 * @brief 打印格式化日志
 */
#define LOG(fmt, ...) printf("%s - " __FILE__ ":" STR(__LINE__) " -> " fmt "\n", get_time(), ##__VA_ARGS__)

char *get_time();

#include <stdlib.h>
#include <time.h>

/**
 * @brief 获取以年月日时分秒的形式表示的当前时间
 * 
 * @return char* 
 */
char *get_time() {
    size_t len = 32;
    char *buffer = (char *)malloc(len);

    time_t rawtime;
    time(&rawtime);
    struct tm *info = localtime(&rawtime);

    strftime(buffer, len, "%Y-%m-%e %H:%M:%S", info);
    return buffer;
}

Rust

https://www.cnblogs.com/develon/p/14818627.html

日志追踪

Rust 有内置的 file!(), line!() 宏,可以追踪日志。目前没有函数名追踪的方法。

#[macro_export]
macro_rules! log {
    ($type: expr, $($arg: tt)+) => {{
        let level: log::Level = $type;
        let str = format!($($arg)+);
        match level {
            log::Level::ERROR => {
                eprintln!("{}:{} -> {}\0", file!(), line!(), str);
            },
            _ => {
                println!("{}:{} -> {}\0", file!(), line!(), str);
            },
        }
    }}
}

还有 dbg!(&a, &b); 能打印代码位置, 变量名及其值:

[src\lib.rs:68] &a = 0
[src\lib.rs:68] &b = 1

Kotlin

日志追踪

通过栈进行追踪。

package lib.log

val log = Logger()

class Logger(private val name: String = "Logger") {
	fun d(vararg _msg: Any?) {
        val msg = _msg.joinToString(separator = " ") {
			it ?.toString() ?: "[null]"
		}

		val trace: Array<StackTraceElement>?  = Thread.currentThread().stackTrace

		if (trace == null || trace.size < 3) return println("Logger@$name $msg")

		val caller = trace[2] // 这里在安卓平台下应该是3(调用栈多了一个dalvik.system.VMStack.getThreadStackTrace())

		with (caller) {
			val output = "Logger@$name ~ $fileName($lineNumber) ~ $className # $methodName() ${ Thread.currentThread().name }: $msg"
			println(output)
		}
	}
}

安卓优化版本:

package lib

import android.util.Log

val log = Logger("Global")

enum class Level {
    Verbose,
    Debug,
    Info,
    Warn,
    Error,
}

class Logger(private val name: String) {
    /**
     * Example:
     * ```
     * import lib.*
     *
     * log.print(Level.Debug, 3, "The value of 1+1:", 1+1)
     * log.e("Error!", RuntimeException("Unknown"))
     * ```
     * @param callerIndex default value: 3
     */
    fun print(_level: Level, callerIndex: Int, vararg _msg: Any?) {
        val level = when (_level) {
            Level.Verbose -> Log.VERBOSE
            Level.Debug -> Log.DEBUG
            Level.Info -> Log.INFO
            Level.Warn -> Log.WARN
            Level.Error -> Log.ERROR
        }
        val msg = _msg.joinToString(separator = " ") {
            it ?.toString() ?: "[null]"
        }

        val trace: Array<StackTraceElement>?  = Thread.currentThread().stackTrace

        if (trace == null || trace.size < callerIndex + 1) {
            Log.println(level, "Logger@$name", msg)
            return
        }

        // 0: dalvik.system.VMStack.getThreadStackTrace(Native Method)
        // 1: java.lang.Thread.getStackTrace(Thread.java:1736)
        // 2: current function, but maybe current function's default function(so, we don't use default params)
        // 3: caller, maybe it's wrapper function d() or e()
        // 4: if called by d() or e() ...etc, this is real caller
        val caller = trace[callerIndex]

        with (caller) {
            val output = "$className#$methodName($fileName:$lineNumber) -> $msg"
            Log.println(level, "Logger@$name", output)
        }
    }

    fun v(vararg _msg: Any?) {
        this.print(Level.Verbose, 4, *_msg)
    }

    fun d(vararg _msg: Any?) {
        this.print(Level.Debug, 4, *_msg)
    }

    fun i(vararg _msg: Any?) {
        this.print(Level.Info, 4, *_msg)
    }

    fun w(vararg _msg: Any?) {
        this.print(Level.Warn, 4, *_msg)
    }

    fun e(vararg _msg: Any?) {
        this.print(Level.Error, 4, *_msg)
    }
}

效果:

2023-06-04 20:35:41.541 /app.xxx D/Logger@Global: app.xxx.RmBindService#onStartCommand(RmBindService.kt:13) -> The value of 1+1: 2
2023-06-04 20:35:41.541 /app.xxx E/Logger@Global: app.xxx.RmBindService#onStartCommand(RmBindService.kt:14) -> Error! java.lang.RuntimeException: Unknown

最新更新:https://github.com/develon2015/remote-bind-apk/blob/main/app/src/main/java/lib/Logger.kt

JavaScript

仰仗 __stack 这个全局变量。
或许 Error#stack 也是个不错的选择:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Error/stack

/**
 * MAGIC GLOBALS
 * https://github.com/gavinengel/magic-globals
 */

/** begin setting magic properties into global (required for other functions) */
Object.defineProperty(global, '__stack', {
  get: function(){
    var orig = Error.prepareStackTrace;
    Error.prepareStackTrace = function(_, stack){ return stack; };
    var err = new Error;
    Error.captureStackTrace(err, arguments.callee);
    var stack = err.stack;
    Error.prepareStackTrace = orig;
    return stack;
  }
});

/** returns line number when placing this in your code: __line */
Object.defineProperty(global, '__line', {
  get: function(){
    return __stack[1].getLineNumber();
  }
});

/** return filename (without directory path or file extension) when placing this in your code: __file */
Object.defineProperty(global, '__file', {
  get: function(){
    var file_pieces = __stack[1].getFileName().split(/[\\\/]/).slice(-1)[0].split('.');
    return file_pieces.slice(0, file_pieces.length - 1).join('.');
  }
});

/** return file extension (without preceding period) when placing this in your code: __ext */
Object.defineProperty(global, '__ext', {
  get: function(){
    return __stack[1].getFileName().split('.').slice(-1)[0];
  }
});

/**
 * return current function
 * @source https://gist.github.com/lordvlad/ec81834ddff73aaa1ab0
 */
Object.defineProperty(global, '__func', {
    get: function(){
        return arguments.callee.caller && arguments.callee.caller.name || '(anonymous)';
    }
});

/** return base path of project */ 
Object.defineProperty(global, '__base', {
  get: function(){
    return process.cwd(); 
  }
});

/** returns filename, a colon, and line number when placing this in your code: __fili */
Object.defineProperty(global, '__fili', {
  get: function(){
    filid = ':'
    if ( typeof GLOBAL.__filid !== 'undefined' && GLOBAL.__filid )
    {
      filid = GLOBAL.__filid;
    }

    return __stack[1].getFileName() + filid + __stack[1].getLineNumber();
  }
});

// ReferenceError: GLOBAL is not defined
const GLOBAL = global;

以下代码在 node.js 下测试通过:

require ( 'magic-globals' ) ;

console.log(__filename) // /home/node/myapp/server/server.js
console.log(__file) // server
console.log(__line) // 6
console.log(__fili) // /home/node/myapp/server/server.js:6
console.log(__ext) // js
console.log(__base) // /home/node/myapp
console.log(__func) // someFunction or (anonymous) 
console.log(__dirname) // /home/node/myapp/server

当然,如果在日志函数里面使用的话,索引不应该是1,而是2.

另一种办法:
文件:log.js,我们将在调用栈里面找改文件名:

/**
 * Use CallSite to extract filename and number, for more info read: https://v8.dev/docs/stack-trace-api#customizing-stack-traces
 * @returns {string} filename and line number separated by a colon
 */
const getFileNameAndLineNumber = () => {
    const oldStackTrace = Error.prepareStackTrace;
    try {
        // eslint-disable-next-line handle-callback-err
        Error.prepareStackTrace = (err, structuredStackTrace) => structuredStackTrace;
        Error.captureStackTrace(this);
        // in this example I needed to "peel" the first CallSites in order to get to the caller we're looking for
        // in your code, the number of stacks depends on the levels of abstractions you're using
        // in my code I'm stripping frames that come from logger module and winston (node_module)
        const callSite = this.stack.find(line => line.getFileName().indexOf('log.js') < 0 && line.getFileName().indexOf('/node_modules/') < 0);
        return callSite.getFileName() + ':' + callSite.getLineNumber();
    } finally {
        Error.prepareStackTrace = oldStackTrace;
    }
};

function log(msg) {
    console.log(getFileNameAndLineNumber(), msg);
}

module.exports = {
    log,
}
posted @ 2023-06-04 17:24  develon  阅读(63)  评论(0编辑  收藏  举报