Android高级——Logger日志系统

Logger日志系统

  • Logger日志系统是基于内核中的Logger日志驱动程序实现
  • 将日志记录保存在内核空间中
  • 使用一个环形缓冲区来保存日志,满了之后,新的日志就会覆盖旧的日志

日志类型

  • main,记录应用程序级别
  • system,记录系统级别
  • radio,记录无线设备相关
  • events,用于诊断系统问题,开发人员不应使用

日志驱动程序

4种类型的日志通过下面4个设备文件来访问

  • /dev/log/main
  • /dev/log/system
  • /dev/log/radio
  • /dev/log/events

运行时库

无论什么类型,最终调用write_to_log写入Logger日志驱动程序

C/C++写入日志

  • 宏ALOGV、ALOGD、ALOGI、ALOGW和ALOGE写入main
  • 宏SLOGV、SLOGD、SLOGI、SLOGW和SLOGE写入system
  • 宏RLOGV、RLOGD、RLOGI、RLOGW和RLOGE写入radio
  • 宏LOG_EVENT_INT、LOG_EVENT_LONG、LOG_EVENT_FLOAT和LOG_EVENT_STRING写入events

Java写入日志

  • android.util.Log 写入main
  • android.util.Slog 写入system
  • android.util.Rlog 写入radio
  • android.util.EventLog 写入event

整体架构

在这里插入图片描述

Logger日志格式

main、system和radio

  • priority:优先级,整数,VERBOSE、DEBUG、INFO、WARN、ERROR和FATAL
  • tag:标签,字符串
  • msg:内容,字符串

在这里插入图片描述

events

  • tag:标签,整数
  • msg:内容,二进制数据,由一个或多个值组成,每个值前面都有一个字段描述它的类型

在这里插入图片描述

tag

tag为整数,根据/system/etc/event-log-tags转为字符串

/system/etc/event-log-tags还用来描述events类型的日志内容的格式

  • tag number:标签值,范围为0~2147483648
  • tag name:标签值对应的字符串描述,字母[A-Z][a-z]、数字[0-9]或者下画线“_”组成
  • 第三个字段:日志内容的值

在这里插入图片描述

值格式为

  • name:名称
  • data type:数据类型,int(1)、long(2)、string(3)、list(4)、float(5)
  • data unit:数据单位,范围是1~6,分别表示对象数量(number of objects)、字节数(Number of bytes)、毫秒数(Number of milliseconds)、分配额(Number of allocations)、标志(ID)和百分比(Percent)

在这里插入图片描述

2722 battery_level (level|1|6),(voltage|1|1),(temperature|1|1)

如上为/system/etc/event-log-tags的内容

  • tag number:2722
  • tag name:battery_level
  • 由三个值组成,level/voltage/temperature,数据类型为1/1/1,数据单位为6/1/1

msg

msg格式为

  • 类型
  • 值,int(1)、long(2)、string(3)、list(4)、float(5)

在这里插入图片描述

Logger日志驱动程序(能力不够暂时分析不了)

基础数据结构

./system/logging/liblog/include/log/log_read.h(Android13)

logger_entry

logger_entry描述一个日志记录,最大长度为4K,其有效负载长度最大等于4K减去结构体logger_entry的大小

  • len:实际log的有效负载长度
  • hdr_size:logger_entry大小
  • pid/itd:进程pid/tid
  • sec/nsec:写入时间
  • lid:实际log id
  • uid:进程uid
struct logger_entry {
  uint16_t len;      /* length of the payload */
  uint16_t hdr_size; /* sizeof(struct logger_entry) */
  int32_t pid;       /* generating process's pid */
  uint32_t tid;      /* generating process's tid */
  uint32_t sec;      /* seconds since Epoch */
  uint32_t nsec;     /* nanoseconds */
  uint32_t lid;      /* log id of the payload, bottom 4 bits currently */
  uint32_t uid;      /* generating process's uid */
};

log_msg

  • 缓冲区5M
  • 包含logger_entry
  • 若为C++,则新增函数返回nsec、lid、msg、len
#define LOGGER_ENTRY_MAX_LEN (5 * 1024)

struct log_msg {
  union {
    unsigned char buf[LOGGER_ENTRY_MAX_LEN + 1];
    struct logger_entry entry;
  } __attribute__((aligned(4)));
#ifdef __cplusplus
  uint64_t nsec() const {
    return static_cast<uint64_t>(entry.sec) * NS_PER_SEC + entry.nsec;
  }
  log_id_t id() {
    return static_cast<log_id_t>(entry.lid);
  }
  char* msg() {
    unsigned short hdr_size = entry.hdr_size;
    if (hdr_size >= sizeof(struct log_msg) - sizeof(entry)) {
      return nullptr;
    }
    return reinterpret_cast<char*>(buf) + hdr_size;
  }
  unsigned int len() { return entry.hdr_size + entry.len; }
#endif
};

main

system/logging/logd/main.cpp,进行初始化操作

  • 默认log时区为utc
  • 缓冲区类型有SerializedLogBuffer(默认)和SimpleLogBuffer,可通过logd.buffer_type属性修改
  • LogReader监听/dev/socket/logdr
  • LogListener监听/dev/socket/logdw
  • CommandListener监听/dev/socket/logd
int main(int argc, char* argv[]) {
    // We want EPIPE when a reader disconnects, not to terminate logd.
    signal(SIGPIPE, SIG_IGN);
    // logd is written under the assumption that the timezone is UTC.
    // If TZ is not set, persist.sys.timezone is looked up in some time utility
    // libc functions, including mktime. It confuses the logd time handling,
    // so here explicitly set TZ to UTC, which overrides the property.
    setenv("TZ", "UTC", 1);
    // issue reinit command. KISS argument parsing.
    if ((argc > 1) && argv[1] && !strcmp(argv[1], "--reinit")) {
        return issueReinit();
    }

    android::base::InitLogging(
            argv, [](android::base::LogId log_id, android::base::LogSeverity severity,
                     const char* tag, const char* file, unsigned int line, const char* message) {
                if (tag && strcmp(tag, "logd") != 0) {
                    auto prefixed_message = android::base::StringPrintf("%s: %s", tag, message);
                    android::base::KernelLogger(log_id, severity, "logd", file, line,
                                                prefixed_message.c_str());
                } else {
                    android::base::KernelLogger(log_id, severity, "logd", file, line, message);
                }
            });

    static const char dev_kmsg[] = "/dev/kmsg";
    int fdDmesg = android_get_control_file(dev_kmsg);
    if (fdDmesg < 0) {
        fdDmesg = TEMP_FAILURE_RETRY(open(dev_kmsg, O_WRONLY | O_CLOEXEC));
    }

    int fdPmesg = -1;
    bool klogd = GetBoolPropertyEngSvelteDefault("ro.logd.kernel");
    if (klogd) {
        SetProperty("ro.logd.kernel", "true");
        static const char proc_kmsg[] = "/proc/kmsg";
        fdPmesg = android_get_control_file(proc_kmsg);
        if (fdPmesg < 0) {
            fdPmesg = TEMP_FAILURE_RETRY(
                open(proc_kmsg, O_RDONLY | O_NDELAY | O_CLOEXEC));
        }
        if (fdPmesg < 0) PLOG(ERROR) << "Failed to open " << proc_kmsg;
    }

    bool auditd = GetBoolProperty("ro.logd.auditd", true);
    DropPrivs(klogd, auditd);

    // A cache of event log tags
    LogTags log_tags;

    // Pruning configuration.
    PruneList prune_list;

    std::string buffer_type = GetProperty("logd.buffer_type", "serialized");

    LogStatistics log_statistics(GetBoolPropertyEngSvelteDefault("logd.statistics"),
                                 buffer_type == "serialized");

    // Serves the purpose of managing the last logs times read on a socket connection, and as a
    // reader lock on a range of log entries.
    LogReaderList reader_list;

    // LogBuffer is the object which is responsible for holding all log entries.
    LogBuffer* log_buffer = nullptr;
    if (buffer_type == "serialized") {
        log_buffer = new SerializedLogBuffer(&reader_list, &log_tags, &log_statistics);
    } else if (buffer_type == "simple") {
        log_buffer = new SimpleLogBuffer(&reader_list, &log_tags, &log_statistics);
    } else {
        LOG(FATAL) << "buffer_type must be one of 'serialized' or 'simple'";
    }

    // LogReader listens on /dev/socket/logdr. When a client
    // connects, log entries in the LogBuffer are written to the client.
    LogReader* reader = new LogReader(log_buffer, &reader_list);
    if (reader->startListener()) {
        return EXIT_FAILURE;
    }

    // LogListener listens on /dev/socket/logdw for client
    // initiated log messages. New log entries are added to LogBuffer
    // and LogReader is notified to send updates to connected clients.
    LogListener* swl = new LogListener(log_buffer);
    if (!swl->StartListener()) {
        return EXIT_FAILURE;
    }

    // Command listener listens on /dev/socket/logd for incoming logd
    // administrative commands.
    CommandListener* cl = new CommandListener(log_buffer, &log_tags, &prune_list, &log_statistics);
    if (cl->startListener()) {
        return EXIT_FAILURE;
    }

    // Notify that others can now interact with logd
    SetProperty("logd.ready", "true");

    // LogAudit listens on NETLINK_AUDIT socket for selinux
    // initiated log messages. New log entries are added to LogBuffer
    // and LogReader is notified to send updates to connected clients.
    LogAudit* al = nullptr;
    if (auditd) {
        int dmesg_fd = GetBoolProperty("ro.logd.auditd.dmesg", true) ? fdDmesg : -1;
        al = new LogAudit(log_buffer, dmesg_fd, &log_statistics);
    }

    LogKlog* kl = nullptr;
    if (klogd) {
        kl = new LogKlog(log_buffer, fdDmesg, fdPmesg, al != nullptr, &log_statistics);
    }

    readDmesg(al, kl);

    // failure is an option ... messages are in dmesg (required by standard)
    if (kl && kl->startListener()) {
        delete kl;
    }

    if (al && al->startListener()) {
        delete al;
    }

    TrustyLog::create(log_buffer);

    TEMP_FAILURE_RETRY(pause());
    return EXIT_SUCCESS;
}

SerializedLogBuffer

  • Log将日志封装成SerializedLogEntry、LogStatisticsElement添加到stats_
SerializedLogBuffer::SerializedLogBuffer(LogReaderList* reader_list, LogTags* tags,
                                         LogStatistics* stats)
    : reader_list_(reader_list), tags_(tags), stats_(stats) {
    Init();
}

void SerializedLogBuffer::Init() {
    log_id_for_each(i) {
        if (!SetSize(i, GetBufferSizeFromProperties(i))) {
            SetSize(i, kLogBufferMinSize);
        }
    }

    // Release any sleeping reader threads to dump their current content.
    auto lock = std::lock_guard{logd_lock};
    for (const auto& reader_thread : reader_list_->running_reader_threads()) {
        reader_thread->TriggerReader();
    }
}

int SerializedLogBuffer::Log(log_id_t log_id, log_time realtime, uid_t uid, pid_t pid, pid_t tid,
                             const char* msg, uint16_t len) {
    if (log_id >= LOG_ID_MAX || len == 0) {
        return -EINVAL;
    }

    if (len > LOGGER_ENTRY_MAX_PAYLOAD) {
        len = LOGGER_ENTRY_MAX_PAYLOAD;
    }

    if (!ShouldLog(log_id, msg, len)) {
        stats_->AddTotal(log_id, len);
        return -EACCES;
    }

    auto sequence = sequence_.fetch_add(1, std::memory_order_relaxed);

    auto lock = std::lock_guard{logd_lock};
    auto entry = LogToLogBuffer(logs_[log_id], max_size_[log_id], sequence, realtime, uid, pid, tid,
                                msg, len);
    stats_->Add(entry->ToLogStatisticsElement(log_id));

    MaybePrune(log_id);

    reader_list_->NotifyNewLog(1 << log_id);
    return len;
}

static SerializedLogEntry* LogToLogBuffer(std::list<SerializedLogChunk>& log_buffer,
                                          size_t max_size, uint64_t sequence, log_time realtime,
                                          uid_t uid, pid_t pid, pid_t tid, const char* msg,
                                          uint16_t len) {
    if (log_buffer.empty()) {
        log_buffer.push_back(SerializedLogChunk(max_size / SerializedLogBuffer::kChunkSizeDivisor));
    }

    auto total_len = sizeof(SerializedLogEntry) + len;
    if (!log_buffer.back().CanLog(total_len)) {
        log_buffer.back().FinishWriting();
        log_buffer.push_back(SerializedLogChunk(max_size / SerializedLogBuffer::kChunkSizeDivisor));
    }

    return log_buffer.back().Log(sequence, realtime, uid, pid, tid, msg, len);
}

LogReader

LogListener

StartListener开启线程LogListener,循环调用HandleData,通过LogBuffer的Log方法写入日志

LogListener::LogListener(LogBuffer* buf) : socket_(GetLogSocket()), logbuf_(buf) {}

bool LogListener::StartListener() {
    if (socket_ <= 0) {
        return false;
    }
    auto thread = std::thread(&LogListener::ThreadFunction, this);
    thread.detach();
    return true;
}

void LogListener::ThreadFunction() {
    prctl(PR_SET_NAME, "logd.writer");

    while (true) {
        HandleData();
    }
}

void LogListener::HandleData() {
    // + 1 to ensure null terminator if MAX_PAYLOAD buffer is received
    __attribute__((uninitialized)) char
            buffer[sizeof(android_log_header_t) + LOGGER_ENTRY_MAX_PAYLOAD + 1];
    struct iovec iov = {buffer, sizeof(buffer) - 1};

    alignas(4) char control[CMSG_SPACE(sizeof(struct ucred))];
    struct msghdr hdr = {
        nullptr, 0, &iov, 1, control, sizeof(control), 0,
    };

    ssize_t n = recvmsg(socket_, &hdr, 0);
    if (n <= (ssize_t)(sizeof(android_log_header_t))) {
        return;
    }

    // To clear the entire buffer would be safe, but this contributes to 1.68%
    // overhead under logging load. We are safe because we check counts, but
    // still need to clear null terminator
    buffer[n] = 0;

    struct ucred* cred = nullptr;

    struct cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr);
    while (cmsg != nullptr) {
        if (cmsg->cmsg_level == SOL_SOCKET &&
            cmsg->cmsg_type == SCM_CREDENTIALS) {
            cred = (struct ucred*)CMSG_DATA(cmsg);
            break;
        }
        cmsg = CMSG_NXTHDR(&hdr, cmsg);
    }

    if (cred == nullptr) {
        return;
    }

    if (cred->uid == AID_LOGD) {
        // ignore log messages we send to ourself.
        // Such log messages are often generated by libraries we depend on
        // which use standard Android logging.
        return;
    }

    android_log_header_t* header =
        reinterpret_cast<android_log_header_t*>(buffer);
    log_id_t logId = static_cast<log_id_t>(header->id);
    if (/* logId < LOG_ID_MIN || */ logId >= LOG_ID_MAX ||
        logId == LOG_ID_KERNEL) {
        return;
    }

    if (logId == LOG_ID_SECURITY) {
        if (!__android_log_security()) {
            return;
        }
        if (!clientCanWriteSecurityLog(cred->uid, cred->gid, cred->pid)) {
            return;
        }
    }

    char* msg = ((char*)buffer) + sizeof(android_log_header_t);
    n -= sizeof(android_log_header_t);

    // NB: hdr.msg_flags & MSG_TRUNC is not tested, silently passing a
    // truncated message to the logs.

    logbuf_->Log(logId, header->realtime, cred->uid, cred->pid, header->tid, msg,
                 ((size_t)n <= UINT16_MAX) ? (uint16_t)n : UINT16_MAX);
}

int LogListener::GetLogSocket() {
    static const char socketName[] = "logdw";
    int sock = android_get_control_socket(socketName);

    if (sock < 0) {  // logd started up in init.sh
        sock = socket_local_server(
            socketName, ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_DGRAM);

        int on = 1;
        if (setsockopt(sock, SOL_SOCKET, SO_PASSCRED, &on, sizeof(on))) {
            return -1;
        }
    }
    return sock;
}

CommandListener

运行时库

write_to_log

system/logging/liblog/logger_write.cpp

调用LogdWrite

#ifdef __ANDROID__
static int write_to_log(log_id_t log_id, struct iovec* vec, size_t nr) {
  int ret;
  struct timespec ts;

  if (log_id == LOG_ID_KERNEL) {
    return -EINVAL;
  }

  clock_gettime(CLOCK_REALTIME, &ts);

  if (log_id == LOG_ID_SECURITY) {
    if (vec[0].iov_len < 4) {
      return -EINVAL;
    }

    ret = check_log_uid_permissions();
    if (ret < 0) {
      return ret;
    }
    if (!__android_log_security()) {
      /* If only we could reset downstream logd counter */
      return -EPERM;
    }
  } else if (log_id == LOG_ID_EVENTS || log_id == LOG_ID_STATS) {
    if (vec[0].iov_len < 4) {
      return -EINVAL;
    }
  }

  ret = LogdWrite(log_id, &ts, vec, nr);
  PmsgWrite(log_id, &ts, vec, nr);

  return ret;
}
#else
static int write_to_log(log_id_t, struct iovec*, size_t) {
  // Non-Android text logs should go to __android_log_stderr_logger, not here.
  // Non-Android binary logs are always dropped.
  return 1;
}
#endif

LogdWrite

system/logging/liblog/logd_writer.cpp

  • 若logId == LOG_ID_SECURITY,获取LogdSocket::BlockingSocket(),否则获取LogdSocket::NonBlockingSocket()
  • 调用sock打开设备/dev/socket/logdw,通过writev写入struct iovec
  • 返回值小于0且错误码不等于EAGAIN需要重新连接再次写入
int LogdWrite(log_id_t logId, struct timespec* ts, struct iovec* vec, size_t nr) {
  ssize_t ret;
  static const unsigned headerLength = 1;
  struct iovec newVec[nr + headerLength];
  android_log_header_t header;
  size_t i, payloadSize;
  static atomic_int dropped;

  LogdSocket& logd_socket =
      logId == LOG_ID_SECURITY ? LogdSocket::BlockingSocket() : LogdSocket::NonBlockingSocket();

  if (logd_socket.sock() < 0) {
    return -EBADF;
  }

  /* logd, after initialization and priv drop */
  if (getuid() == AID_LOGD) {
    /*
     * ignore log messages we send to ourself (logd).
     * Such log messages are often generated by libraries we depend on
     * which use standard Android logging.
     */
    return 0;
  }

  header.tid = gettid();
  header.realtime.tv_sec = ts->tv_sec;
  header.realtime.tv_nsec = ts->tv_nsec;

  newVec[0].iov_base = (unsigned char*)&header;
  newVec[0].iov_len = sizeof(header);

  int32_t snapshot = atomic_exchange_explicit(&dropped, 0, memory_order_relaxed);
  if (snapshot && __android_log_is_loggable_len(ANDROID_LOG_INFO, "liblog", strlen("liblog"),
                                                ANDROID_LOG_VERBOSE)) {
    android_log_event_int_t buffer;

    header.id = LOG_ID_EVENTS;
    buffer.header.tag = LIBLOG_LOG_TAG;
    buffer.payload.type = EVENT_TYPE_INT;
    buffer.payload.data = snapshot;

    newVec[headerLength].iov_base = &buffer;
    newVec[headerLength].iov_len = sizeof(buffer);

    ret = TEMP_FAILURE_RETRY(writev(logd_socket.sock(), newVec, 2));
    if (ret != (ssize_t)(sizeof(header) + sizeof(buffer))) {
      atomic_fetch_add_explicit(&dropped, snapshot, memory_order_relaxed);
    }
  }

  header.id = logId;

  for (payloadSize = 0, i = headerLength; i < nr + headerLength; i++) {
    newVec[i].iov_base = vec[i - headerLength].iov_base;
    payloadSize += newVec[i].iov_len = vec[i - headerLength].iov_len;

    if (payloadSize > LOGGER_ENTRY_MAX_PAYLOAD) {
      newVec[i].iov_len -= payloadSize - LOGGER_ENTRY_MAX_PAYLOAD;
      if (newVec[i].iov_len) {
        ++i;
      }
      break;
    }
  }

  // EAGAIN occurs if logd is overloaded, other errors indicate that something went wrong with
  // the connection, so we reset it and try again.
  ret = TEMP_FAILURE_RETRY(writev(logd_socket.sock(), newVec, i));
  if (ret < 0 && errno != EAGAIN) {
    logd_socket.Reconnect();

    ret = TEMP_FAILURE_RETRY(writev(logd_socket.sock(), newVec, i));
  }

  if (ret < 0) {
    ret = -errno;
  }

  if (ret > (ssize_t)sizeof(header)) {
    ret -= sizeof(header);
  } else if (ret < 0) {
    atomic_fetch_add_explicit(&dropped, 1, memory_order_relaxed);
  }

  return ret;
}

LogdSocket

sock、GetSocket、LogdConnect打开设备/dev/socket/logdw

class LogdSocket {
 public:
  static LogdSocket& BlockingSocket() {
    static LogdSocket logd_socket(true);
    return logd_socket;
  }
  static LogdSocket& NonBlockingSocket() {
    static LogdSocket logd_socket(false);
    return logd_socket;
  }

  void Reconnect() { LogdConnect(sock_); }

  // Zygote uses this to clean up open FD's after fork() and before specialization.  It is single
  // threaded at this point and therefore this function is explicitly not thread safe.  It sets
  // sock_ to kUninitialized, so future logs will be safely initialized whenever they happen.
  void Close() {
    if (sock_ != kUninitialized) {
      close(sock_);
    }
    sock_ = kUninitialized;
  }

  int sock() {
    GetSocket();
    return sock_;
  }

 private:
  LogdSocket(bool blocking) : blocking_(blocking) {}

  // Note that it is safe to call connect() multiple times on DGRAM Unix domain sockets, so this
  // function is used to reconnect to logd without requiring a new socket.
  static void LogdConnect(int sock) {
    sockaddr_un un = {};
    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, "/dev/socket/logdw");
    TEMP_FAILURE_RETRY(connect(sock, reinterpret_cast<sockaddr*>(&un), sizeof(sockaddr_un)));
  }

  // sock_ should only be opened once.  If we see that sock_ is uninitialized, we
  // create a new socket and attempt to exchange it into the atomic sock_.  If the
  // compare/exchange was successful, then that will be the socket used for the duration of the
  // program, otherwise a different thread has already opened and written the socket to the atomic,
  // so close the new socket and return.
  void GetSocket() {
    if (sock_ != kUninitialized) {
      return;
    }

    int flags = SOCK_DGRAM | SOCK_CLOEXEC;
    if (!blocking_) {
      flags |= SOCK_NONBLOCK;
    }
    int new_socket = TEMP_FAILURE_RETRY(socket(PF_UNIX, flags, 0));
    if (new_socket < 0) {
      return;
    }

    LogdConnect(new_socket);

    int uninitialized_value = kUninitialized;
    if (!sock_.compare_exchange_strong(uninitialized_value, new_socket)) {
      close(new_socket);
      return;
    }
  }

  static const int kUninitialized = -1;
  atomic_int sock_ = kUninitialized;
  bool blocking_;
};

__android_log_print

system/logging/liblog/logger_write.cpp

int __android_log_print(int prio, const char* tag, const char* fmt, ...) {
  ErrnoRestorer errno_restorer;

  if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
    return -EPERM;
  }

  va_list ap;
  __attribute__((uninitialized)) char buf[LOG_BUF_SIZE];

  va_start(ap, fmt);
  vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
  va_end(ap);

  __android_log_message log_message = {
      sizeof(__android_log_message), LOG_ID_MAIN, prio, tag, nullptr, 0, buf};
  __android_log_write_log_message(&log_message);
  return 1;
}

__android_log_write_log_message

void __android_log_write_log_message(__android_log_message* log_message) {
  ErrnoRestorer errno_restorer;

  if (log_message->buffer_id != LOG_ID_DEFAULT && log_message->buffer_id != LOG_ID_MAIN &&
      log_message->buffer_id != LOG_ID_SYSTEM && log_message->buffer_id != LOG_ID_RADIO &&
      log_message->buffer_id != LOG_ID_CRASH) {
    return;
  }

  if (log_message->tag == nullptr) {
    log_message->tag = GetDefaultTag().c_str();
  }

#if __BIONIC__
  if (log_message->priority == ANDROID_LOG_FATAL) {
    android_set_abort_message(log_message->message);
  }
#endif

  get_logger_function()(log_message);
}

调用get_logger_function,get_file_logger_path判断是否有定义ro.log.file_logger.path指定log文件路径,如果没有则调用__android_log_logd_logger

static __android_logger_function get_logger_function() {
  if (user_set_logger_function != nullptr) {
    return user_set_logger_function;
  }
  static __android_logger_function default_logger_function = []() {
#if __ANDROID__
    if (get_file_logger_path() != nullptr) {
      return file_logger;
    } else {
      return __android_log_logd_logger;
    }
#else
    return file_logger;
#endif
  }();
  return default_logger_function;
}

#ifdef __ANDROID__
static const char* get_file_logger_path() {
  static const char* file_logger_path = []() {
    static char path[PROP_VALUE_MAX] = {};
    if (__system_property_get("ro.log.file_logger.path", path) > 0) {
      return path;
    }
    return (char*)nullptr;  // means file_logger should not be used
  }();
  return file_logger_path;
}
#endif

__android_log_logd_logger将优先级、标签、内容存在数字元素vec[0]、vec[1]和vec[2],最后调用write_to_log

+1是因为标签和内容后面跟着’\0’,用来区分和解析

void __android_log_logd_logger(const struct __android_log_message* log_message) {
  int buffer_id = log_message->buffer_id == LOG_ID_DEFAULT ? LOG_ID_MAIN : log_message->buffer_id;

  struct iovec vec[3];
  vec[0].iov_base =
      const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(&log_message->priority));
  vec[0].iov_len = 1;
  vec[1].iov_base = const_cast<void*>(static_cast<const void*>(log_message->tag));
  vec[1].iov_len = strlen(log_message->tag) + 1;
  vec[2].iov_base = const_cast<void*>(static_cast<const void*>(log_message->message));
  vec[2].iov_len = strlen(log_message->message) + 1;

  write_to_log(static_cast<log_id_t>(buffer_id), vec, 3);
}

__android_log_buf_print

也是调用__android_log_write_log_message,同上

int __android_log_buf_print(int bufID, int prio, const char* tag, const char* fmt, ...) {
  ErrnoRestorer errno_restorer;

  if (!__android_log_is_loggable(prio, tag, ANDROID_LOG_VERBOSE)) {
    return -EPERM;
  }

  va_list ap;
  __attribute__((uninitialized)) char buf[LOG_BUF_SIZE];

  va_start(ap, fmt);
  vsnprintf(buf, LOG_BUF_SIZE, fmt, ap);
  va_end(ap);

  __android_log_message log_message = {
      sizeof(__android_log_message), bufID, prio, tag, nullptr, 0, buf};
  __android_log_write_log_message(&log_message);
  return 1;
}

__android_log_bwrite/__android_log_btwrite/__android_log_bswrite

  • __android_log_bwrite 的内容可由多个值组成
  • __android_log_btwrite 的内容只有一个值,类型为参数type
  • __android_log_btwrite 的内容为字符串
int __android_log_bwrite(int32_t tag, const void* payload, size_t len) {
  ErrnoRestorer errno_restorer;

  struct iovec vec[2];

  vec[0].iov_base = &tag;
  vec[0].iov_len = sizeof(tag);
  vec[1].iov_base = (void*)payload;
  vec[1].iov_len = len;

  return write_to_log(LOG_ID_EVENTS, vec, 2);
}

int __android_log_btwrite(int32_t tag, char type, const void* payload, size_t len) {
  ErrnoRestorer errno_restorer;

  struct iovec vec[3];

  vec[0].iov_base = &tag;
  vec[0].iov_len = sizeof(tag);
  vec[1].iov_base = &type;
  vec[1].iov_len = sizeof(type);
  vec[2].iov_base = (void*)payload;
  vec[2].iov_len = len;

  return write_to_log(LOG_ID_EVENTS, vec, 3);
}

int __android_log_bswrite(int32_t tag, const char* payload) {
  ErrnoRestorer errno_restorer;

  struct iovec vec[4];
  char type = EVENT_TYPE_STRING;
  uint32_t len = strlen(payload);

  vec[0].iov_base = &tag;
  vec[0].iov_len = sizeof(tag);
  vec[1].iov_base = &type;
  vec[1].iov_len = sizeof(type);
  vec[2].iov_base = &len;
  vec[2].iov_len = sizeof(len);
  vec[3].iov_base = (void*)payload;
  vec[3].iov_len = len;

  return write_to_log(LOG_ID_EVENTS, vec, 4);
}

# C/C++写入日志

system/logging/liblog/include/log/log.h(Android13)

属性LOG_NDEBUG限制Log的输出(为0时相关函数定义为空)

#ifndef LOG_NDEBUG
#ifdef NDEBUG
#define LOG_NDEBUG 1
#else
#define LOG_NDEBUG 0
#endif
#endif

属性LOG_TAG定义了当前编译单元的日志TAG,默认为空

#ifndef LOG_TAG
#define LOG_TAG NULL
#endif

ALOGV 、ALOGD 、ALOGI 、ALOGW 和ALOGE

/system/logging/liblog/include/log/log_main.h,ALOGV只有当LOG_NDEBUG为0时才有效

#ifndef ALOGV
#define __ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__))
#if LOG_NDEBUG
#define ALOGV(...)                   \
  do {                               \
    __FAKE_USE_VA_ARGS(__VA_ARGS__); \
    if (false) {                     \
      __ALOGV(__VA_ARGS__);          \
    }                                \
  } while (false)
#else
#define ALOGV(...) __ALOGV(__VA_ARGS__)
#endif
#endif

#ifndef ALOGD
#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#endif

#ifndef ALOGI
#define ALOGI(...) ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__))
#endif

#ifndef ALOGW
#define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__))
#endif

#ifndef ALOGE
#define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__))
#endif

调用ALOG、LOG_PRI、android_printLog,最后调用运行时库的__android_log_print

#ifndef ALOG
#define ALOG(priority, tag, ...) LOG_PRI(ANDROID_##priority, tag, __VA_ARGS__)
#endif

#ifndef LOG_PRI
#define LOG_PRI(priority, tag, ...) android_printLog(priority, tag, __VA_ARGS__)
#endif

#define android_printLog(prio, tag, ...) \
  __android_log_print(prio, tag, __VA_ARGS__)

RLOGV、RLOGD、RLOGI、RLOGW和RLOGE

/system/logging/liblog/include/log/log_radio.h

同理,RLOGV只有在LOG_NDEBUG为0才有效,最后调用运行时库的__android_log_buf_print,传入LOG_ID_RADIO

#ifndef RLOGV
#define __RLOGV(...)                                                         \
  ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_VERBOSE, LOG_TAG, \
                                 __VA_ARGS__))
#if LOG_NDEBUG
#define RLOGV(...)          \
  do {                      \
    if (0) {                \
      __RLOGV(__VA_ARGS__); \
    }                       \
  } while (0)
#else
#define RLOGV(...) __RLOGV(__VA_ARGS__)
#endif
#endif

#ifndef RLOGD
#define RLOGD(...)                                                         \
  ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_DEBUG, LOG_TAG, \
                                 __VA_ARGS__))
#endif

#ifndef RLOGI
#define RLOGI(...)                                                        \
  ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_INFO, LOG_TAG, \
                                 __VA_ARGS__))
#endif

#ifndef RLOGW
#define RLOGW(...)                                                        \
  ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_WARN, LOG_TAG, \
                                 __VA_ARGS__))
#endif

#ifndef RLOGE
#define RLOGE(...)                                                         \
  ((void)__android_log_buf_print(LOG_ID_RADIO, ANDROID_LOG_ERROR, LOG_TAG, \
                                 __VA_ARGS__))
#endif

SLOGV、SLOGD、SLOGI、SLOGW和SLOGE

/system/logging/liblog/include/log/log_system.h

同理,SLOGV只有在LOG_NDEBUG为0才有效,最后调用运行时库的__android_log_buf_print,传入LOG_ID_SYSTEM

#ifndef SLOGV
#define __SLOGV(...)                                                          \
  ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_VERBOSE, LOG_TAG, \
                                 __VA_ARGS__))
#if LOG_NDEBUG
#define SLOGV(...)          \
  do {                      \
    if (0) {                \
      __SLOGV(__VA_ARGS__); \
    }                       \
  } while (0)
#else
#define SLOGV(...) __SLOGV(__VA_ARGS__)
#endif
#endif

#ifndef SLOGD
#define SLOGD(...)                                                          \
  ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_DEBUG, LOG_TAG, \
                                 __VA_ARGS__))
#endif

#ifndef SLOGI
#define SLOGI(...)                                                         \
  ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_INFO, LOG_TAG, \
                                 __VA_ARGS__))
#endif

#ifndef SLOGW
#define SLOGW(...)                                                         \
  ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_WARN, LOG_TAG, \
                                 __VA_ARGS__))
#endif

#ifndef SLOGE
#define SLOGE(...)                                                          \
  ((void)__android_log_buf_print(LOG_ID_SYSTEM, ANDROID_LOG_ERROR, LOG_TAG, \
                                 __VA_ARGS__))
#endif

LOG_EVENT_INT、LOG_EVENT_LONG、LOG_EVENT_FLOAT和LOG_EVENT_STRING

system/logging/liblog/include/log/log.h

  • LOG_EVENT_INT、LOG_EVENT_LONG和LOG_EVENT_LONG调用android_btWriteLog,最后调用运行时库的__android_log_btwrite
  • LOG_EVENT_STRING调用运行时库的__android_log_bswrite
#define android_btWriteLog(tag, type, payload, len) \
  __android_log_btwrite(tag, type, payload, len)

typedef enum {
  /* Special markers for android_log_list_element type */
  EVENT_TYPE_LIST_STOP = '\n', /* declare end of list  */
  EVENT_TYPE_UNKNOWN = '?',    /* protocol error       */

  /* must match with declaration in java/android/android/util/EventLog.java */
  EVENT_TYPE_INT = 0,  /* int32_t */
  EVENT_TYPE_LONG = 1, /* int64_t */
  EVENT_TYPE_STRING = 2,
  EVENT_TYPE_LIST = 3,
  EVENT_TYPE_FLOAT = 4,
} AndroidEventLogType;

#ifndef LOG_EVENT_INT
#define LOG_EVENT_INT(_tag, _value)                                          \
  {                                                                          \
    int intBuf = _value;                                                     \
    (void)android_btWriteLog(_tag, EVENT_TYPE_INT, &intBuf, sizeof(intBuf)); \
  }
#endif

#ifndef LOG_EVENT_LONG
#define LOG_EVENT_LONG(_tag, _value)                                            \
  {                                                                             \
    long long longBuf = _value;                                                 \
    (void)android_btWriteLog(_tag, EVENT_TYPE_LONG, &longBuf, sizeof(longBuf)); \
  }
#endif

#ifndef LOG_EVENT_FLOAT
#define LOG_EVENT_FLOAT(_tag, _value)                           \
  {                                                             \
    float floatBuf = _value;                                    \
    (void)android_btWriteLog(_tag, EVENT_TYPE_FLOAT, &floatBuf, \
                             sizeof(floatBuf));                 \
  }
#endif

#ifndef LOG_EVENT_STRING
#define LOG_EVENT_STRING(_tag, _value) \
  (void)__android_log_bswrite(_tag, _value);
#endif

Java写入日志

android.util.Log

frameworks/base/core/java/android/util/Log.java

public final class Log {
   ......

    /**
     * Priority constant for the println method; use Log.v.
     */
    public static final int VERBOSE = 2;

    /**
     * Priority constant for the println method; use Log.d.
     */
    public static final int DEBUG = 3;

    /**
     * Priority constant for the println method; use Log.i.
     */
    public static final int INFO = 4;

    /**
     * Priority constant for the println method; use Log.w.
     */
    public static final int WARN = 5;

    /**
     * Priority constant for the println method; use Log.e.
     */
    public static final int ERROR = 6;

    /**
     * Priority constant for the println method.
     */
    public static final int ASSERT = 7;

	......

    public static int v(@Nullable String tag, @NonNull String msg) {
        return println_native(LOG_ID_MAIN, VERBOSE, tag, msg);
    }

    public static int d(@Nullable String tag, @NonNull String msg) {
        return println_native(LOG_ID_MAIN, DEBUG, tag, msg);
    }

    public static int i(@Nullable String tag, @NonNull String msg) {
        return println_native(LOG_ID_MAIN, INFO, tag, msg);
    }

    public static int w(@Nullable String tag, @NonNull String msg) {
        return println_native(LOG_ID_MAIN, WARN, tag, msg);
    }

    public static int e(@Nullable String tag, @NonNull String msg) {
        return println_native(LOG_ID_MAIN, ERROR, tag, msg);
    }

	......

    /** @hide */ public static final int LOG_ID_MAIN = 0;
    /** @hide */ public static final int LOG_ID_RADIO = 1;
    /** @hide */ public static final int LOG_ID_EVENTS = 2;
    /** @hide */ public static final int LOG_ID_SYSTEM = 3;
    /** @hide */ public static final int LOG_ID_CRASH = 4;	
	......
}

调用println_native,传入LOG_ID_MAIN

println_native

根据frameworks/base/core/jni/android_util_Log.cpp

static const JNINativeMethod gMethods[] = {
	......
    { "println_native",  "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },
  	......
};

可知对应的调用函数,判断日志内容msgObj 是否为空,判断类型是否在[0, LOG_ID_MAX],最后调用运行时库的__android_log_buf_write写入log

static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
        jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
    const char* tag = NULL;
    const char* msg = NULL;

    if (msgObj == NULL) {
        jniThrowNullPointerException(env, "println needs a message");
        return -1;
    }

    if (bufID < 0 || bufID >= LOG_ID_MAX) {
        jniThrowNullPointerException(env, "bad bufID");
        return -1;
    }

    if (tagObj != NULL)
        tag = env->GetStringUTFChars(tagObj, NULL);
    msg = env->GetStringUTFChars(msgObj, NULL);

    int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);

    if (tag != NULL)
        env->ReleaseStringUTFChars(tagObj, tag);
    env->ReleaseStringUTFChars(msgObj, msg);

    return res;
}

android.util.Slog

frameworks/base/core/java/android/util/Slog.java

只可在系统内部使用,同上调用Log中的println_native传入LOG_ID_SYSTEM

public final class Slog {
	
	......
	
	@UnsupportedAppUsage
    public static int v(@Nullable String tag, @NonNull String msg) {
        return Log.println_native(Log.LOG_ID_SYSTEM, Log.VERBOSE, tag, msg);
    }
	
	@UnsupportedAppUsage
    public static int d(@Nullable String tag, @NonNull String msg) {
        return Log.println_native(Log.LOG_ID_SYSTEM, Log.DEBUG, tag, msg);
    }

    @UnsupportedAppUsage
    public static int i(@Nullable String tag, @NonNull String msg) {
        return Log.println_native(Log.LOG_ID_SYSTEM, Log.INFO, tag, msg);
    }

    @UnsupportedAppUsage
    public static int w(@Nullable String tag, @NonNull String msg) {
        return Log.println_native(Log.LOG_ID_SYSTEM, Log.WARN, tag, msg);
    }

    @UnsupportedAppUsage
    public static int e(@Nullable String tag, @NonNull String msg) {
        return Log.println_native(Log.LOG_ID_SYSTEM, Log.ERROR, tag, msg);
    }
}

android.util.Rlog

frameworks/base/core/java/android/util/Rlog.java

只可在系统内部使用,同上调用Log中的println_native传入LOG_ID_RADIO

public final class Rlog {
	
	.....

    @UnsupportedAppUsage
    public static int v(String tag, String msg) {
        return Log.println_native(Log.LOG_ID_RADIO, Log.VERBOSE, tag, msg);
    }

    @UnsupportedAppUsage
    public static int d(String tag, String msg) {
        return Log.println_native(Log.LOG_ID_RADIO, Log.DEBUG, tag, msg);
    }

    @UnsupportedAppUsage
    public static int i(String tag, String msg) {
        return Log.println_native(Log.LOG_ID_RADIO, Log.INFO, tag, msg);
    }

    @UnsupportedAppUsage
    public static int w(String tag, String msg) {
        return Log.println_native(Log.LOG_ID_RADIO, Log.WARN, tag, msg);
    }

    @UnsupportedAppUsage
    public static int e(String tag, String msg) {
        return Log.println_native(Log.LOG_ID_RADIO, Log.ERROR, tag, msg);
    }

    public static int println(int priority, String tag, String msg) {
        return Log.println_native(Log.LOG_ID_RADIO, priority, tag, msg);
    }
}

android.util.EventLog

frameworks/base/core/java/android/util/EventLog.java,重载了5个版本的writeEvent方法,日志内容分别为int、long、float、string、list

public class EventLog {
	......
	
	private static final byte INT_TYPE    = 0;
    private static final byte LONG_TYPE   = 1;
    private static final byte STRING_TYPE = 2;
    private static final byte LIST_TYPE   = 3;
    private static final byte FLOAT_TYPE = 4;
   
    public static native int writeEvent(int tag, int value);

    public static native int writeEvent(int tag, long value);

    public static native int writeEvent(int tag, float value);

    public static native int writeEvent(int tag, String str);

    public static native int writeEvent(int tag, Object... list);
	
	......
}

writeEvent

根据frameworks/base/core/jni/android_util_EventLog.cpp

static const JNINativeMethod gRegisterMethods[] = {
    /* name, signature, funcPtr */
    { "writeEvent", "(II)I", (void*) ELog::writeEventInteger },
    { "writeEvent", "(IJ)I", (void*) ELog::writeEventLong },
    { "writeEvent", "(IF)I", (void*) ELog::writeEventFloat },
    { "writeEvent", "(ILjava/lang/String;)I", (void*) ELog::writeEventString },
    { "writeEvent", "(I[Ljava/lang/Object;)I", (void*) ELog::writeEventArray },
    ......
};

可知调用

static jint writeEventInteger(JNIEnv* env ATTRIBUTE_UNUSED, jobject clazz ATTRIBUTE_UNUSED,
        jint tag, jint value) {
    android_log_event_list ctx(tag);
    ctx << (int32_t)value;
    return ctx.write(LogID);
}

static jint writeEventLong(JNIEnv* env ATTRIBUTE_UNUSED, jobject clazz ATTRIBUTE_UNUSED,
        jint tag, jlong value) {
    android_log_event_list ctx(tag);
    ctx << (int64_t)value;
    return ctx.write(LogID);
}

static jint writeEventFloat(JNIEnv* env ATTRIBUTE_UNUSED, jobject clazz ATTRIBUTE_UNUSED,
        jint tag, jfloat value) {
    android_log_event_list ctx(tag);
    ctx << (float)value;
    return ctx.write(LogID);
}

static jint writeEventString(JNIEnv* env, jobject clazz ATTRIBUTE_UNUSED, jint tag,
        jstring value) {
    android_log_event_list ctx(tag);
    // Don't throw NPE -- I feel like it's sort of mean for a logging function
    // to be all crashy if you pass in NULL -- but make the NULL value explicit.
    ctx << (value != nullptr ? ScopedUtfChars(env, value).c_str() : "NULL");
    return ctx.write(LogID);
}

static jint writeEventArray(JNIEnv* env, jobject clazz ATTRIBUTE_UNUSED, jint tag,
        jobjectArray value) {
    android_log_event_list ctx(tag);
    if (value == nullptr) {
        ctx << "[NULL]";
        return ctx.write(LogID);
    }
    jsize copied = 0, num = env->GetArrayLength(value);
    for (; copied < num && copied < 255; ++copied) {
        if (ctx.status()) break;
        ScopedLocalRef<jobject> item(env, env->GetObjectArrayElement(value, copied));
        if (item == nullptr) {
            ctx << "NULL";
        } else if (env->IsInstanceOf(item.get(), gStringClass)) {
            ctx << ScopedUtfChars(env, (jstring) item.get()).c_str();
        } else if (env->IsInstanceOf(item.get(), gIntegerClass)) {
            ctx << (int32_t)env->GetIntField(item.get(), gIntegerValueID);
        } else if (env->IsInstanceOf(item.get(), gLongClass)) {
            ctx << (int64_t)env->GetLongField(item.get(), gLongValueID);
        } else if (env->IsInstanceOf(item.get(), gFloatClass)) {
            ctx << (float)env->GetFloatField(item.get(), gFloatValueID);
        } else {
            jniThrowException(env,
                    "java/lang/IllegalArgumentException",
                    "Invalid payload item type");
            return -1;
        }
    }
    return ctx.write(LogID);
}

log_event_list

system/logging/liblog/include/log/log_event_list.h

int android_log_write_list(android_log_context ctx, log_id_t id);

class android_log_event_list {

	......
    int write(log_id_t id = LOG_ID_EVENTS) {
    /* facilitate -EBUSY retry */
    if ((ret == -EBUSY) || (ret > 0)) ret = 0;
    int retval = android_log_write_list(ctx, id);
    /* existing errors trump transmission errors */
    if (!ret) ret = retval;
    return ret;
  }
   ......
}

system/logging/liblog/log_event_list.cpp,根据id == LOG_ID_EVENTS调用__android_log_bwrite

int android_log_write_list(android_log_context context, log_id_t id) {
  const char* msg;
  ssize_t len;

  if ((id != LOG_ID_EVENTS) && (id != LOG_ID_SECURITY) && (id != LOG_ID_STATS)) {
    return -EINVAL;
  }

  if (!context || (kAndroidLoggerWrite != context->read_write_flag)) {
    return -EBADF;
  }
  if (context->list_nest_depth) {
    return -EIO;
  }
  /* NB: if there was overflow, then log is truncated. Nothing reported */
  context->storage[1] = context->count[0];
  len = context->len = context->pos;
  msg = (const char*)context->storage;
  /* it's not a list */
  if (context->count[0] <= 1) {
    len -= sizeof(uint8_t) + sizeof(uint8_t);
    if (len < 0) {
      len = 0;
    }
    msg += sizeof(uint8_t) + sizeof(uint8_t);
  }
  return (id == LOG_ID_EVENTS)
             ? __android_log_bwrite(context->tag, msg, len)
             : ((id == LOG_ID_STATS) ? __android_log_stats_bwrite(context->tag, msg, len)
                                     : __android_log_security_bwrite(context->tag, msg, len));
}

Logcat工具分析

基础数据结构

logcat

system/logging/logcat/logcat.cpp

int main(int argc, char** argv) {
    Logcat logcat;
    return logcat.Run(argc, argv);
}

下面来分析其Run方法

命令参数

int Logcat::Run(int argc, char** argv) {
   	......
    while (true) {
    ......
        switch (c) {
        	......
            case 'd':
                mode |= ANDROID_LOG_NONBLOCK;
                break;

            case 't':
                got_t = true;
                mode |= ANDROID_LOG_NONBLOCK;
                FALLTHROUGH_INTENDED;
            case 'T':
                if (strspn(optarg, "0123456789") != strlen(optarg)) {
                    char* cp = parseTime(tail_time, optarg);
                    if (!cp) {
                        error(EXIT_FAILURE, 0, "-%c '%s' not in time format.", c, optarg);
                    }
                    if (*cp) {
                        char ch = *cp;
                        *cp = '\0';
                        fprintf(stderr, "WARNING: -%c '%s' '%c%s' time truncated\n", c, optarg, ch,
                                cp + 1);
                        *cp = ch;
                    }
                } else {
                    if (!ParseUint(optarg, &tail_lines) || tail_lines < 1) {
                        fprintf(stderr, "WARNING: -%c %s invalid, setting to 1\n", c, optarg);
                        tail_lines = 1;
                    }
                }
                break;

            case 'D':
                print_dividers_ = true;
                break;

            case 'e':
                regex_.reset(new std::regex(optarg));
                break;

            case 'm': {
                if (!ParseUint(optarg, &max_count_) || max_count_ < 1) {
                    error(EXIT_FAILURE, 0, "-%c '%s' isn't an integer greater than zero.", c,
                          optarg);
                }
            } break;

            case 'g':
                if (!optarg) {
                    getLogSize = true;
                    break;
                }
                FALLTHROUGH_INTENDED;

            case 'G': {
                if (!ParseByteCount(optarg, &setLogSize) || setLogSize < 1) {
                    error(EXIT_FAILURE, 0, "-G must be specified as <num><multiplier>.");
                }
            } break;

            case 'p':
                if (!optarg) {
                    getPruneList = true;
                    break;
                }
                FALLTHROUGH_INTENDED;

            case 'P':
                setPruneList = optarg;
                break;
            case 'b':
                for (const auto& buffer : Split(optarg, delimiters)) {
                    if (buffer == "default") {
                        id_mask |= (1 << LOG_ID_MAIN) | (1 << LOG_ID_SYSTEM) | (1 << LOG_ID_CRASH);
                    } else if (buffer == "all") {
                        id_mask = -1;
                    } else {
                        log_id_t log_id = android_name_to_log_id(buffer.c_str());
                        if (log_id >= LOG_ID_MAX) {
                            error(EXIT_FAILURE, 0, "Unknown buffer '%s' listed for -b.",
                                  buffer.c_str());
                        }
                        if (log_id == LOG_ID_SECURITY) {
                            security_buffer_selected = true;
                        }
                        id_mask |= (1 << log_id);
                    }
                }
                break;

            case 'B':
                print_binary_ = 1;
                break;

            case 'f':
                if ((tail_time == log_time::EPOCH) && !tail_lines) {
                    tail_time = lastLogTime(optarg);
                }
                // redirect output to a file
                output_file_name_ = optarg;
                break;
            case 'r':
                if (!ParseUint(optarg, &log_rotate_size_kb_) || log_rotate_size_kb_ < 1) {
                    error(EXIT_FAILURE, 0, "Invalid parameter '%s' to -r.", optarg);
                }
                break;

            case 'n':
                if (!ParseUint(optarg, &max_rotated_logs_) || max_rotated_logs_ < 1) {
                    error(EXIT_FAILURE, 0, "Invalid parameter '%s' to -n.", optarg);
                }
                break;

            case 'v':
                for (const auto& arg : Split(optarg, delimiters)) {
                    int err = SetLogFormat(arg.c_str());
                    if (err < 0) {
                        error(EXIT_FAILURE, 0, "Invalid parameter '%s' to -v.", arg.c_str());
                    }
                    if (err) hasSetLogFormat = true;
                }
                break;

            case 'S':
                printStatistics = true;
                break;

            case ':':
                error(EXIT_FAILURE, 0, "Option '%s' needs an argument.", argv[optind - 1]);
                break;

            case 'h':
                show_help();
                return EXIT_SUCCESS;

            case '?':
                error(EXIT_FAILURE, 0, "Unknown option '%s'.", argv[optind]);
                break;

            default:
                error(EXIT_FAILURE, 0, "Unknown getopt_long() result '%c'.", c);
        }
    }
  • d 把 mode设为ANDROID_LOG_NONBLOCK,表示没有日志记录可读时logcat直接退出
  • t 将got_t 设为true,表示只输出最新的日志
  • b 将参数分割出来,通过id_mask设置读取的设备
  • B print_binary_ = 1,表示以二进制输出日志
  • f 指定输出文件output_file_name_
  • r 指定输出文件的大小log_rotate_size_kb_(默认0无限制)
  • n 指定输出文件的个数max_rotated_logs_(默认4),若输出日志时,已超过-r指定大小,则建立新的日志文件,格式为xxx.1/xxx.2/xxx.n
  • v 调用SetLogFormat设置日志输出格式,将参数转为AndroidLogPrintFormat并设置到p_format->format
int Logcat::SetLogFormat(const char* format_string) {
    AndroidLogPrintFormat format = android_log_formatFromString(format_string);

    // invalid string?
    if (format == FORMAT_OFF) return -1;

    return android_log_setPrintFormat(logformat_.get(), format);
}

system/logging/liblog/logprint.cpp

AndroidLogPrintFormat android_log_formatFromString(const char* formatString) {
  /* clang-format off */
  if (!strcmp(formatString, "brief")) return FORMAT_BRIEF;
  if (!strcmp(formatString, "process")) return FORMAT_PROCESS;
  if (!strcmp(formatString, "tag")) return FORMAT_TAG;
  if (!strcmp(formatString, "thread")) return FORMAT_THREAD;
  if (!strcmp(formatString, "raw")) return FORMAT_RAW;
  if (!strcmp(formatString, "time")) return FORMAT_TIME;
  if (!strcmp(formatString, "threadtime")) return FORMAT_THREADTIME;
  if (!strcmp(formatString, "long")) return FORMAT_LONG;
  if (!strcmp(formatString, "color")) return FORMAT_MODIFIER_COLOR;
  if (!strcmp(formatString, "colour")) return FORMAT_MODIFIER_COLOR;
  if (!strcmp(formatString, "usec")) return FORMAT_MODIFIER_TIME_USEC;
  if (!strcmp(formatString, "nsec")) return FORMAT_MODIFIER_TIME_NSEC;
  if (!strcmp(formatString, "printable")) return FORMAT_MODIFIER_PRINTABLE;
  if (!strcmp(formatString, "year")) return FORMAT_MODIFIER_YEAR;
  if (!strcmp(formatString, "zone")) return FORMAT_MODIFIER_ZONE;
  if (!strcmp(formatString, "epoch")) return FORMAT_MODIFIER_EPOCH;
  if (!strcmp(formatString, "monotonic")) return FORMAT_MODIFIER_MONOTONIC;
  if (!strcmp(formatString, "uid")) return FORMAT_MODIFIER_UID;
  if (!strcmp(formatString, "descriptive")) return FORMAT_MODIFIER_DESCRIPT;
    /* clang-format on */

#if !defined(__MINGW32__)
  // Check whether the format string is actually a time zone. If tzname[0]
  // is the empty string, that's tzset() signalling that it doesn't know
  // the requested timezone.
  TzSetter tz(formatString);
  if (!*tzname[0]) {
    tz.Reset();
  } else {
    // We keep the new time zone as a side effect!
    return FORMAT_MODIFIER_ZONE;
  }
#endif

  return FORMAT_OFF;
}

int android_log_setPrintFormat(AndroidLogFormat* p_format, AndroidLogPrintFormat format) {
  switch (format) {
    case FORMAT_MODIFIER_COLOR:
      p_format->colored_output = true;
      return 0;
    case FORMAT_MODIFIER_TIME_USEC:
      p_format->usec_time_output = true;
      return 0;
    case FORMAT_MODIFIER_TIME_NSEC:
      p_format->nsec_time_output = true;
      return 0;
    case FORMAT_MODIFIER_PRINTABLE:
      p_format->printable_output = true;
      return 0;
    case FORMAT_MODIFIER_YEAR:
      p_format->year_output = true;
      return 0;
    case FORMAT_MODIFIER_ZONE:
      p_format->zone_output = !p_format->zone_output;
      return 0;
    case FORMAT_MODIFIER_EPOCH:
      p_format->epoch_output = true;
      return 0;
    case FORMAT_MODIFIER_MONOTONIC:
      p_format->monotonic_output = true;
      return 0;
    case FORMAT_MODIFIER_UID:
      p_format->uid_output = true;
      return 0;
    case FORMAT_MODIFIER_DESCRIPT:
      p_format->descriptive_output = true;
      descriptive_output = true;
      return 0;
    default:
      break;
  }
  p_format->format = format;
  return 1;
}

日志的格式为<PREFIX>+MESSAGE+<SUFFIX>,不同格式的<PREFIX>和<SUFFIX>不同

  • FORMAT_BRIEF:“<priority>/<tag>(<pid>):”和“\n”。

  • FORMAT_PROCESS:“<priority>(<pid>)”和“(<t a g>)\n”。

  • FORMAT_TAG:“<priority>/(<tag>):”和“\n”。

  • FORMAT_THREAD:“<priority>(<pid>:<tid>)”和“\n”。

  • FORMAT_RAW:空值和“\n”。

  • FORMAT_TIME:“<sec>.<nsec> <priority>/<tag>(<pid>):”和“\n”。

  • FORMAT_THREADTIME:“<sec>.<nsec><pid><tid><priority><tag>:”和“\n”。

  • FORMAT_LONG:“[<sec>.<nsec> <pid>:<tid><priority>/<tag>]”和“\n\n”

其他

上面解析完参数,继续往后走,未指定选项b时,默认输出MAIN、system、crash、kernel的log

    // If no buffers are specified, default to using these buffers.
    if (id_mask == 0) {
        id_mask = (1 << LOG_ID_MAIN) | (1 << LOG_ID_SYSTEM) | (1 << LOG_ID_CRASH) |
                  (1 << LOG_ID_KERNEL);
    }

未设置选项v时,将环境变量ANDROID_PRINTF_LOG的值设置为当前格式,若无则设置为threadtime

    if (!hasSetLogFormat) {
        const char* logFormat = getenv("ANDROID_PRINTF_LOG");

        if (!!logFormat) {
            for (const auto& arg : Split(logFormat, delimiters)) {
                int err = SetLogFormat(arg.c_str());
                // environment should not cause crash of logcat
                if (err < 0) {
                    fprintf(stderr, "invalid format in ANDROID_PRINTF_LOG '%s'\n", arg.c_str());
                }
                if (err > 0) hasSetLogFormat = true;
            }
        }
        if (!hasSetLogFormat) {
            SetLogFormat("threadtime");
        }
    }
  • forceFilters.size()不为0,表示通过选项Q让logcat读取/proc/cmdline中的过滤器
  • argc == optind表示命令没有其他参数,读取ANDROID_LOG_TAGS的值作为过滤器
  • 将命令参数设置为过滤器,格式为 tag:priority,如 *:E
    if (forceFilters.size()) {
        int err = android_log_addFilterString(logformat_.get(), forceFilters.c_str());
        if (err < 0) {
            error(EXIT_FAILURE, 0, "Invalid filter expression in logcat args.");
        }
    } else if (argc == optind) {
        // Add from environment variable
        const char* env_tags_orig = getenv("ANDROID_LOG_TAGS");

        if (!!env_tags_orig) {
            int err = android_log_addFilterString(logformat_.get(), env_tags_orig);

            if (err < 0) {
                error(EXIT_FAILURE, 0, "Invalid filter expression in ANDROID_LOG_TAGS.");
            }
        }
    } else {
        // Add from commandline
        for (int i = optind ; i < argc ; i++) {
            int err = android_log_addFilterString(logformat_.get(), argv[i]);
            if (err < 0) {
                error(EXIT_FAILURE, 0, "Invalid filter expression '%s'.", argv[i]);
            }
        }
    }

通过android_log_addFilterString设置过滤器,filterString可能以空格、tab或逗号分割

int android_log_addFilterString(AndroidLogFormat* p_format, const char* filterString) {
  char* filterStringCopy = strdup(filterString);
  char* p_cur = filterStringCopy;
  char* p_ret;
  int err;

  /* Yes, I'm using strsep */
  while (NULL != (p_ret = strsep(&p_cur, " \t,"))) {
    /* ignore whitespace-only entries */
    if (p_ret[0] != '\0') {
      err = android_log_addFilterRule(p_format, p_ret);

      if (err < 0) {
        goto error;
      }
    }
  }

  free(filterStringCopy);
  return 0;
error:
  free(filterStringCopy);
  return -1;
}

通过android_log_addFilterRule添加解析过滤器,将冒号后面的字符转为android_LogPriority,通过tagName和pri创建FilterInfo

int android_log_addFilterRule(AndroidLogFormat* p_format, const char* filterExpression) {
  size_t tagNameLength;
  android_LogPriority pri = ANDROID_LOG_DEFAULT;

  tagNameLength = strcspn(filterExpression, ":");

  if (tagNameLength == 0) {
    goto error;
  }

  if (filterExpression[tagNameLength] == ':') {
    pri = filterCharToPri(filterExpression[tagNameLength + 1]);

    if (pri == ANDROID_LOG_UNKNOWN) {
      goto error;
    }
  }

  if (0 == strncmp("*", filterExpression, tagNameLength)) {
    /*
     * This filter expression refers to the global filter
     * The default level for this is DEBUG if the priority
     * is unspecified
     */
    if (pri == ANDROID_LOG_DEFAULT) {
      pri = ANDROID_LOG_DEBUG;
    }

    p_format->global_pri = pri;
  } else {
    /*
     * for filter expressions that don't refer to the global
     * filter, the default is verbose if the priority is unspecified
     */
    if (pri == ANDROID_LOG_DEFAULT) {
      pri = ANDROID_LOG_VERBOSE;
    }

    char* tagName;

/*
 * Presently HAVE_STRNDUP is never defined, so the second case is always taken
 * Darwin doesn't have strndup, everything else does
 */
#ifdef HAVE_STRNDUP
    tagName = strndup(filterExpression, tagNameLength);
#else
    /* a few extra bytes copied... */
    tagName = strdup(filterExpression);
    tagName[tagNameLength] = '\0';
#endif /*HAVE_STRNDUP*/

    FilterInfo* p_fi = filterinfo_new(tagName, pri);
    free(tagName);

    p_fi->p_next = p_format->filters;
    p_format->filters = p_fi;
  }

  return 0;
error:
  return -1;
}

static android_LogPriority filterCharToPri(char c) {
  android_LogPriority pri;

  c = tolower(c);

  if (c >= '0' && c <= '9') {
    if (c >= ('0' + ANDROID_LOG_SILENT)) {
      pri = ANDROID_LOG_VERBOSE;
    } else {
      pri = (android_LogPriority)(c - '0');
    }
  } else if (c == 'v') {
    pri = ANDROID_LOG_VERBOSE;
  } else if (c == 'd') {
    pri = ANDROID_LOG_DEBUG;
  } else if (c == 'i') {
    pri = ANDROID_LOG_INFO;
  } else if (c == 'w') {
    pri = ANDROID_LOG_WARN;
  } else if (c == 'e') {
    pri = ANDROID_LOG_ERROR;
  } else if (c == 'f') {
    pri = ANDROID_LOG_FATAL;
  } else if (c == 's') {
    pri = ANDROID_LOG_SILENT;
  } else if (c == '*') {
    pri = ANDROID_LOG_DEFAULT;
  } else {
    pri = ANDROID_LOG_UNKNOWN;
  }

  return pri;
}

若通过选项f指定输出文件,通过max_rotated_logs_设置个数,格式为xxx.1/…/xxx.n

    if (output_file_name_) {
        if (setLogSize || getLogSize || printStatistics || getPruneList || setPruneList) {
            error(EXIT_FAILURE, 0, "-f is incompatible with -g/-G, -S, and -p/-P.");
        }

        if (clearLog || setId) {
            int max_rotation_count_digits =
                    max_rotated_logs_ > 0 ? (int)(floor(log10(max_rotated_logs_) + 1)) : 0;

            for (int i = max_rotated_logs_; i >= 0; --i) {
                std::string file;

                if (!i) {
                    file = output_file_name_;
                } else {
                    file = StringPrintf("%s.%.*d", output_file_name_, max_rotation_count_digits, i);
                }

                int err = unlink(file.c_str());

                if (err < 0 && errno != ENOENT) {
                    fprintf(stderr, "failed to delete log file '%s': %s\n", file.c_str(),
                            strerror(errno));
                }
            }
        }

        if (clearLog) {
            return EXIT_SUCCESS;
        }
    }

根据id打开log设备

    for (int i = LOG_ID_MIN; i < LOG_ID_MAX; ++i) {
        if (!(id_mask & (1 << i))) continue;
        const char* buffer_name = android_log_id_to_name(static_cast<log_id_t>(i));

        auto logger = android_logger_open(logger_list.get(), static_cast<log_id_t>(i));
        if (logger == nullptr) {
            ReportErrorName(buffer_name, security_buffer_selected, &open_device_failures);
            continue;
        }
        ......

判断输出是二进制调用WriteFully,否则调用ProcessBuffer

    while (!max_count_ || print_count_ < max_count_) {
        ......
        if (print_binary_) {
            WriteFully(&log_msg, log_msg.len());
        } else {
            ProcessBuffer(&log_msg);
        }
        if (blocking && output_file_ == stdout) fflush(stdout);
    }
    return EXIT_SUCCESS;
}
  • 若日志类型是Event,调用android_log_processBinaryLogBuffer,否则调用android_log_processLogBuffer,他们都会将buf转为AndroidLogEntry用于输出
  • android_log_shouldPrintLine 判断tag和pri是否符合设置的过滤条件
  • 当日志超出大小时,调用RotateLogs建立新文件
void Logcat::ProcessBuffer(struct log_msg* buf) {
    AndroidLogEntry entry;
    char binaryMsgBuf[1024] __attribute__((__uninitialized__));

    bool is_binary =
            buf->id() == LOG_ID_EVENTS || buf->id() == LOG_ID_STATS || buf->id() == LOG_ID_SECURITY;
    int err;
    if (is_binary) {
        if (!event_tag_map_ && !has_opened_event_tag_map_) {
            event_tag_map_.reset(android_openEventTagMap(nullptr));
            has_opened_event_tag_map_ = true;
        }
        // This causes entry to point to binaryMsgBuf!
        err = android_log_processBinaryLogBuffer(&buf->entry, &entry, event_tag_map_.get(),
                                                 binaryMsgBuf, sizeof(binaryMsgBuf));

        // printf(">>> pri=%d len=%d msg='%s'\n",
        //    entry.priority, entry.messageLen, entry.message);
    } else {
        err = android_log_processLogBuffer(&buf->entry, &entry);
    }
    if (err < 0 && !debug_) return;

    if (android_log_shouldPrintLine(logformat_.get(), std::string(entry.tag, entry.tagLen).c_str(),
                                    entry.priority)) {
        bool match = !regex_ ||
                     std::regex_search(entry.message, entry.message + entry.messageLen, *regex_);

        print_count_ += match;
        if (match || print_it_anyway_) {
            PrintDividers(buf->id(), print_dividers_);
            out_byte_count_ += android_log_printLogLine(logformat_.get(), output_file_, &entry);
        }
    }

    if (log_rotate_size_kb_ > 0 && (out_byte_count_ / 1024) >= log_rotate_size_kb_) {
        RotateLogs();
    }
}

android_log_printLogLine时输出的最后一个步骤,调用android_log_formatLogLine格式化要输出的日志,调用fwrite把日志输出到文件描述符fd所描述的目标文件中

size_t android_log_printLogLine(AndroidLogFormat* p_format, FILE* fp,
                                const AndroidLogEntry* entry) {
  char buf[4096] __attribute__((__uninitialized__));
  size_t line_length;
  char* line = android_log_formatLogLine(p_format, buf, sizeof(buf), entry, &line_length);
  if (!line) {
    fprintf(stderr, "android_log_formatLogLine failed\n");
    exit(1);
  }

  size_t bytesWritten = fwrite(line, 1, line_length, fp);
  if (bytesWritten != line_length) {
    perror("fwrite failed");
    exit(1);
  }

  if (line != buf) free(line);
  return bytesWritten;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/792315.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vue3打包发布,刷新出现的空白页面和错误

Vue3打包发布出现的错误&#xff1a;Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of text/html. Strict MIME type checking is enforced for module scripts per HTML spec. 第一次点击访问到这个路径&…

北斗GPS天线使用技巧与性能对比

北斗GPS天线使用中注意的问题 多系统兼容性&#xff1a;确保天线不仅能接收北斗信号&#xff0c;还能同时接收其他GNSS系统&#xff08;如GPS、GLONASS、Galileo&#xff09;的信号&#xff0c;以提高定位精度和可靠性。 信号频率选择&#xff1a;根据应用需求选择合适的信号…

MFC Ribbon菜单 - 中英文实时切换方法

简介 最近在搞一个老外的项目&#xff0c;本来谈的好好的&#xff0c;纯英文界面。项目接近尾声了&#xff0c;又提出了中英文实时切换的新需求&#xff0c;没办法就只能想办法&#xff0c;毕竟客户最大嘛。 实现方法 还好本来的ribbon英文菜单不复杂&#xff0c;就用纯C编码…

谷歌插件之一键关闭同域名页面

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 &#x1f38f;&#xff1a;你只管努力&#xff0c;剩下的交给时间 &#x1f3e0; &#xff1a;小破站 谷歌插件之一键关闭同域名页面 前言项目结构mainfest.jsonbackgroud.js 项目实现效果展示展望 前…

【手把手教你使用cgroup配置,十分钟就会】

手把手教你使用cgroup配置&#xff0c;十分钟就会 什么是cgroupcgroup中的参数概念及原理 以 memory为例看下如何配置配置内存限制写一个内存申请脚本执行脚本测试结束语 什么是cgroup cgroups 是Linux内核提供的一种可以限制单个进程或者多个进程所使用资源的机制&#xff0c…

论文降痕降重全攻略:从技巧到工具,助你轻松应对学术挑战

AIGC降重工具&#xff1a;快速降低论文查重率 高查重率是许多毕业生的困扰。通常&#xff0c;高查重率源于过度引用未经修改的参考资料和格式错误。传统的降重方法&#xff0c;如修改文本和增添原创内容&#xff0c;虽必要但耗时且成效不一。 鉴于此&#xff0c;应用AI工具进…

未来互联网的新篇章:深度解析Web3技术

随着技术的不断演进&#xff0c;Web3正逐渐成为引领未来互联网发展的关键驱动力。本文将深入探讨Web3技术的核心概念、关键特征以及其对未来互联网生态的深远影响&#xff0c;旨在帮助读者全面理解和把握这一新兴技术的发展方向和潜力。 1. Web3的基本概念和演进 Web3并非简单…

WindChill软件许可优化解决方案

WindChill软件介绍 WindChill作为PLM系统&#xff0c; 提供了帮助制造商在产品生命周期的各个阶段管理自己的产品的完整功能&#xff0c;其功能强大、高性能的体系结构正是为当今的全球环境而设计的&#xff0c;帮助公司提高生产效率并改善产品质量和性能。 WindChill许可问题…

每日一练 - 理解IGMP组播组信息

下面是路由器 RTB 的部分输出信息&#xff0c; 关于输出信息描述错误的是A.接口上动态加入的组播组个数是 1 B.加入的组播组地址是 225.1.1.2 C.dsplay igmp group 命令用来查看 IGMP 组播组信息,包括通过成员报告动态加入的组播组和通过命令行静态加入的组播组信息 D.最后发…

让你的终端出现花哨明了的打印

本文代码使用较为简单&#xff0c;主要就是为了高亮打印&#xff0c;直接用即可 代码如下&#xff1a; /*** file cout.h* author BigDavid* brief * version 0.1* date 2024-07-10* * copyright Copyright (c) 2024* */ #pragma once #include<stdio.h>#include<uni…

自学鸿蒙HarmonyOS的ArkTS语言<六>警告弹窗AlertDialog和列表选择弹窗ActionSheet

一、警告弹窗 ... Button(点击我可以获取一个警告弹窗).onClick(() > {AlertDialog.show({title: 我是弹窗标题,subtitle: 我是副标题,message: 我是弹窗内容,autoCancel: true, // 点击遮罩层是否关闭alignment: DialogAlignment.Center, // 弹窗位置offset: { dx: 0, dy:…

手机通讯录大营救,恢复sim卡联系人的3个重要方法

在数字化世界的浩瀚海洋中&#xff0c;手机通讯录就像一艘承载着人际关系的生命之船。然而&#xff0c;当这艘船遭遇风浪&#xff0c;即sim卡上的联系人信息意外丢失时&#xff0c;我们该如何进行一场惊心动魄的大营救&#xff0c;找回那些珍贵的联系人呢&#xff1f;别担心&am…

springboot服装购物商城系统-计算机毕业设计源码35058

摘要 服装购物商城系统小程序&#xff0c;依托Spring Boot框架的强大支持&#xff0c;为用户呈现了一个功能丰富、体验流畅的在线购物平台。该系统不仅涵盖了商品展示、用户注册登录、购物车管理、订单处理、支付集成等核心购物流程&#xff0c;还引入了个性化推荐算法&#xf…

排序(一)——冒泡排序、直接插入排序、希尔排序(BubbleSOrt,InsertSort,ShellSort)

欢迎来到繁星的CSDN&#xff0c;本期的内容主要包括冒泡排序(BubbleSort&#xff09;&#xff0c;直接插入排序(InsertSort)&#xff0c;以及插入排序进阶版希尔排序&#xff08;ShellSort&#xff09;。 废话不多说&#xff0c;直接上正题&#xff01; 一、冒泡排序 冒泡排序…

Lumos学习王佩丰Excel第四讲:排序与选择

一、排序 1、简单排序&#xff1a;不要选中一列排序&#xff0c;不然只是局部排序&#xff0c;其他数据都会发生错乱。 2、多条件排序 3、2003版本中超过3个排序条件时如何处理&#xff1a;从最后一个条件到第一个条件倒着按照要求依次排序。 4、按颜色排序 5、自定义排序次序…

探索Kotlin:从K1到K2

人不走空 &#x1f308;个人主页&#xff1a;人不走空 &#x1f496;系列专栏&#xff1a;算法专题 ⏰诗词歌赋&#xff1a;斯是陋室&#xff0c;惟吾德馨 嘿&#xff0c;小伙伴们&#xff01;今天我们来聊聊Kotlin&#xff0c;这个在安卓开发圈里越来越火的编程语言。…

YoloV8改进策略:卷积篇|Kan行天下之GRAM,KAN遇见Gram多项式V2版本

GRAM&#xff08;GRAM可能是一个新提出的模型或方法的缩写&#xff0c;这里我们根据上下文进行解释&#xff09;受到诸如TorchKAN和ChebyKAN等Kolmogorov-Arnold网络&#xff08;KAN&#xff09;替代方案的启发。GRAM引入了一种简化的KAN模型&#xff0c;但同时利用了Gram多项式…

paddla模型转gguf

在使用ollama配置本地模型时&#xff0c;只支持gguf格式的模型&#xff0c;所以我们首先需要把自己的模型转化为bin格式&#xff0c;本文为paddle&#xff0c;onnx&#xff0c;pytorch格式的模型提供说明&#xff0c;safetensors格式比较简单请参考官方文档&#xff0c;或其它教…

Docker存储目录问题,如何修改Docker默认存储位置?(Docker存储路径、Docker存储空间)etc/docker/daemon.json

文章目录 如何更改docker默认存储路径&#xff1f;版本1&#xff08;没测试&#xff09;版本2&#xff08;可行&#xff09;1. 停止 Docker 服务&#xff1a;2. 创建新的存储目录&#xff1a;3. 修改 Docker 配置文件&#xff1a;4. 移动现有的 Docker 数据&#xff1a;5. 重新…

【Pytorch】Conda环境下载慢换源/删源/恢复默认源

文章目录 背景临时换源永久换源打开conda配置condarc换源执行配置 命令行修改源添加源查看源 删源恢复默认源使用示范 背景 随着实验增多&#xff0c;需要分割创建环境的情况时有出现&#xff0c;在此情况下使用conda create --name xx python3.10 pytorch torchvision pytorc…