展开菜单
首页 精品内容 本月促销 装机必备 Windows PDF教程 macOS软件 IOS软件 Android AI 专题
全部分类

当前位置:

首页 > 编程开发 > 加班之余从零写了个 spdlog 风格的日志库

加班之余从零写了个 spdlog 风格的日志库

从零实现spdlog风格C++日志库,采用分层架构:common.h定义日志消息结构,formatter.h将模式串编译为flag_formatter链表,sink.h通过模板参数实现锁策略,async_logger.h使用阻塞队列异步写日志,registry.h作为全局单例管理。通过亲手实现理解设计模式与工程细节。

从零手写 C++ 日志库 —— 一个 spdlog 风格 logger 的完整实现


spdlog 是 C++ 日志库的事实标准,header-only、性能炸裂、功能齐全。但说实话,每次翻开它的源码都有点想放弃——模板嵌套、工厂模式、fmt 集成,太多高级特性堆在一起,根本看不清架构脉络。

加班之余从零写了个 spdlog 风格的日志库

有一天突然冒出一个想法:与其硬看源码,不如自己从零写一个。目标不是替代 spdlog,而是理解它为什么长成那个样子。

从零写一个日志库,真实的好处是:一个设计点都逃不掉。用 spdlog 的时候 logger->info("hello") 一行就结束了。但自己实现这一行,背后每个决策都得想清楚——消息怎么存、格式串怎么编、sink 要不要锁、异步队列怎么设计。等你全部亲手做过一遍,再看 spdlog 源码就会发现没什么神秘的了——那些设计模式你在自己实现的过程中已经摸过,剩下的只是工程细节。

这篇文章按代码的分层结构走,每一层都会解释"它要解决什么问题、为什么这样设计、最关键的代码是什么"。


项目结构

第一版写了两天——一个 580 行的单头文件,所有类堆在一起。大家可能不信,那个版本能跑,也能看懂,但回看真的累。上周才拆成了模块:

 复制代码my_logger/
├── logger.h                  — 聚合头文件,用户只 include 这一个
└── core/
    ├── common.h              — 日志级别、消息结构体、跨平台适配
    ├── blocking_queue.h      — 线程安全阻塞队列
    ├── formatter.h           — 策略模式:%flag → formatter 链表
    ├── sink.h                — 多态输出:控制台/文件/滚动文件
    ├── logger.h              — 日志记录器
    ├── async_logger.h        — 异步日志(后台线程写 sink)
    ├── registry.h            — 全局单例注册表
    └── macros.h              — LOG_INFO / LOG_ERROR 等宏

跟 muduo 的分层思路一样——每层只做一件事,替换任何一层都不影响其他层。


第一层:common.h — 消息结构

一条日志长什么样?其实就是这几个字段:

 复制代码enum class level {
    trace = 0, debug = 1, info = 2, warn = 3, err = 4, crit = 5, off = 6
};struct log_msg {
    std::string logger_name;                            // 哪个 logger 产生的
    level lvl;                                          // 日志级别
    std::string payload;                                // 用户传进来的消息正文
    std::chrono::system_clock::time_point time;         // 产生时刻
    size_t thread_id;                                   // 哪个线程产生的
};

就是一个结构体——把一次日志调用的所有信息打包,下一步交给 formatter 处理。thread_id 是用 std::hash 算出来的简短数字,方便输出。

这里有个坑:localtime 是 C 标准库函数,Windows 和 Linux 的线程安全版本不一样。Windows 用 localtime_s,Linux 用 localtime_r。写了一个小包装:

 复制代码inline void localtime_platform(const std::time_t* t, std::tm* tm) {
#ifdef _WIN32
    localtime_s(tm, t);
#else
    localtime_r(t, tm);
#endif
}

第二层:formatter.h — 格式串怎么编译

这是整个库最有意思的设计。用户传一个模式串:

 复制代码"[%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v"

最终输出:

 复制代码[2026-07-13 23:15:30.123] [myapp] [info] hello world

问题来了:这段模式串怎么处理?最简单的做法是 format() 里用 if/else 逐个字符解析——遇到 % 就看看后面是什么、拼一段再继续走。能跑,但性能差。每条日志都要重新分析模式串,O(n) 字符串扫描。

spdlog 的思路是把模式串编译成一个对象数组。编译只做一次(构造时),运行时直接遍历对象数组执行虚函数——零解析开销。

flag_formatter 继承体系

每个 %flag 对应一个子类:

 复制代码class flag_formatter {
public:
    virtual ~flag_formatter() = default;
    virtual void format(const log_msg& msg, const std::tm& tm_time,
                        std::string& dest) = 0;
};

然后 %Y → year_flag,%m → month_flag,%d → day_flag,依此类推。每个子类就干一件小事:

 复制代码class year_flag : public flag_formatter {
public:
    void format(const log_msg&, const std::tm& tm, std::string& dest) override {
        dest += std::to_string(tm.tm_year + 1900);
    }
};class message_flag : public flag_formatter {
public:
    void format(const log_msg& msg, const std::tm&, std::string& dest) override {
        dest += msg.payload;
    }
};

编译 → 执行

 复制代码class pattern_formatter {
    std::vector> formatters_;    void compile_pattern_(const std::string& pattern) {
        std::string literal;    // 攒着 %flag 之间的普通文本块
        for (auto it = pattern.begin(); it != pattern.end(); ++it) {
            if (*it == '%' && std::next(it) != pattern.end()) {
                // 先把之前攒的文本块输出
                if (!literal.empty()) {
                    formatters_.push_back(make_unique(move(literal)));
                    literal.clear();
                }
                // 读取 flag 字符,创建对应的 formatter
                char flag = *++it;
                formatters_.push_back(create_flag_(flag));
            } else {
                literal += *it;
            }
        }
        // 剩的文本块(如果有)
        if (!literal.empty())
            formatters_.push_back(make_unique(move(literal)));
    }    // 实际执行格式化就是简单遍历
    void format(const log_msg& msg, std::string& dest) {
        for (auto& f : formatters_) {
            f->format(msg, cached_tm_, dest);
        }
        dest += 'n';
    }
};

逻辑很直接:遍历模式串,% 后面跟的字符是一个 flag 指令,其他字符是普通的文字块。literal_flag 直接追加文字,name_flag 追加 logger 名称,level_flag 追加级别名称,等等。

时间缓存

还有一个重要的优化。一条消息可能发给多个 sink(比如同时输出到屏幕和文件),如果每个 sink 都调一次 localtime,浪费不小。实际上同一条消息的时间不可能变——秒级之内都一样。所以缓冲了一个 cached_tm_,秒变了才重新调 localtime:

 复制代码void format(const log_msg& msg, std::string& dest) {
    auto secs = duration_cast(msg.time.time_since_epoch());
    if (secs != last_log_secs_) {        // 秒变了才更新
        time_t t = system_clock::to_time_t(msg.time);
        localtime_platform(&t, &cached_tm_);
        last_log_secs_ = secs;
    }
    for (auto& f : formatters_)
        f->format(msg, cached_tm_, dest);
    dest += 'n';
}

第三层:sink.h — 多态输出 + 模板注入锁

日志的输出目标可以是控制台、文件、甚至网络。每种目标行为不同——控制台直接 cout,文件需要 ofstream,滚动文件还需要检测大小、重命名。但它们有统一的接口:

 复制代码class sink {
public:
    virtual ~sink() = default;
    virtual void log(level lvl, const std::string& msg) = 0;
    virtual void flush() = 0;    void set_level(level lvl) { level_ = lvl; }
    level get_level() const { return level_; }    bool should_log(level msg_level) {
        return static_cast<int>(msg_level) >= static_cast<int>(level_);
    }protected:
    level level_{ level::trace };
};

sink 只定义接口,具体的输出行为留给子类。同时每个 sink 有自己的 level 阈值——比如 stdout_sink 设 info,file_sink 设 trace,就能做到"屏幕只看重要信息,文件记录全部"。

锁策略——模板注入

多线程下写文件需要加锁,但控制台输出版本如果是单线程就不需要锁——白费 CPU。怎么让一份代码同时支持两种锁策略?答案是模板参数:

 复制代码// 空锁——编译后直接消失,零开销
struct null_mutex {
    void lock() {}
    void unlock() {}
};// 模板基类:通过 Mutex 参数决定加不加锁
template <typename Mutex = null_mutex>
class base_sink : public sink {
public:
    void log(level lvl, const std::string& msg) final {
        std::lock_guard lock(mtx_);    // 如果是 null_mutex,lock() 是空函数
        sink_it_(lvl, msg);                    // → 编译器内联,直接消失
    }
    void flush() final {
        std::lock_guard lock(mtx_);
        flush_();
    }
protected:
    virtual void sink_it_(level lvl, const std::string& msg) = 0;
    virtual void flush_() = 0;
    Mutex mtx_;
};

子类继承的时候选模板参数:

 复制代码// 控制台——默认单线程,template 用的是 null_mutex
class stdout_sink : public base_sink<> {
    void sink_it_(level, const std::string& msg) override { std::cout << msg; }
    void flush_()   override { std::cout << std::flush; }
};// 文件——多线程安全,template 用的是 std::mutex
class file_sink : public base_sink {
    std::ofstream file_;
public:
    file_sink(const std::string& filename) { file_.open(filename); }
    void sink_it_(level, const std::string& msg) override { file_ << msg; }
    void flush_()   override { file_ << std::flush; }
};

这就是"编译期多态"——template 不是运行时选的,是编译时就确定了。null_mutex 版本在生成的汇编里根本没有锁操作。

滚动文件 sink

生产环境最实用的 sink:文件大到一定尺寸(比如 1MB),它会关掉旧文件、给它改个名(app_20260713_231530.log),然后开一个新 app.log 继续写。核心是 std::rename 这个系统调用,原子操作,跨平台。

 复制代码void rotate_() {
    auto now = system_clock::now();
    auto t = system_clock::to_time_t(now);
    tm tm_buf;
    localtime_platform(&t, &tm_buf);
    char ts[32];
    strftime(ts, sizeof(ts), "%Y%m%d-%H%M%S", &tm_buf);    auto old = base_name_ + ".log";
    auto rotated = base_name_ + "_" + ts + ".log";
    rename(old.c_str(), rotated.c_str());
}

第四层:logger.h — 组合 formatter + sink

有了 formatter 和 sink,logger 的工作就是把两者串起来:

 复制代码class logger {
    std::string name_;
    level level_{ level::info };
    std::unique_ptr formatter_;
    std::vector> sinks_;   // 可以有多个 sinkpublic:
    void log(level lvl, const std::string& payload) {
        if (!should_log(lvl)) return;               // ① 级别过滤        log_msg msg;                                // ② 打包消息
        msg.logger_name = name_;
        msg.lvl = lvl;
        msg.payload = payload;
        msg.time = system_clock::now();
        msg.thread_id = hash{}(this_thread::get_id());        std::string formatted;
        formatter_->format(msg, formatted);         // ③ 格式化        for (auto& s : sinks_) {                    // ④ 分发给每个 sink
            if (s->should_log(lvl))
                s->log(lvl, formatted);
        }
    }    // 加一个 sink
    void add_sink(std::shared_ptr s) { sinks_.push_back(move(s)); }
};

用户实际用的便捷接口:

 复制代码void trace(const std::string& msg) { log(level::trace, msg); }
void debug(const std::string& msg) { log(level::debug, msg); }
void info(const std::string& msg)  { log(level::info,  msg); }
void warn(const std::string& msg)  { log(level::warn,  msg); }
void error(const std::string& msg) { log(level::err,   msg); }
void crit(const std::string& msg)  { log(level::crit,  msg); }

第五层:async_logger.h — 异步日志

同步 logger 有一个性能坑:写文件是 I/O 操作,可能慢,会阻塞业务线程。解决方案是把"写 sink"这个慢操作移到后台线程去,业务线程只负责格式化消息和丢队列。

blocking_queue — 生产者消费者

 复制代码template <typename T>
class blocking_queue {
    std::queue q_;
    std::mutex mtx_;
    std::condition_variable cv_;
    bool stopped_ = false;    void enqueue(T item) {
        { lock_guard lock(mtx_); q_.push(move(item)); }
        cv_.notify_one();                      // 唤醒一个等待的消费者
    }    optional dequeue() {
        unique_lock lock(mtx_);
        cv_.wait(lock, [this] { return !q_.empty() || stopped_; });
        if (stopped_ && q_.empty()) return nullopt;  // 退出了
        T item = move(q_.front());
        q_.pop();
        return item;
    }    void stop() {
        { lock_guard lock(mtx_); stopped_ = true; }
        cv_.notify_all();                      // 唤醒所有消费者
    }
};

async_logger 包装

 复制代码class async_logger {
    shared_ptr backend_;        // 同步 logger(做格式化)
    blocking_queue q_;       // 消息队列
    vector workers_;            // 后台线程池    void log(level lvl, const string& payload) {
        // 第一步:格式化(前端线程做,快)
        log_msg msg = ...;
        string formatted;
        backend_->format(msg, formatted);        // 第二步:丢队列(前端线程的活干完了)
        async_msg amsg{lvl, move(formatted)};
        q_.enqueue(move(amsg));
    }    // 后台线程:出队 → 写 sink(慢,不在业务线程上)
    void run_() {
        while (true) {
            auto msg = q_.dequeue();
            if (!msg) break;
            backend_->log_formatted(msg->lvl, msg->formatted);
        }
    }    ~async_logger() {
        q_.stop();          // 停止队列
        for (auto& t : workers_) t.join();  // 等所有 worker 刷盘
    }
};

析构时 q_.stop() → 所有 worker 被唤醒 → 把队列里的还没处理完的消息刷盘 → join() 等所有线程退出。优雅关闭,不会丢日志。


第六层:registry.h — 全局管理

照着 spdlog 的 registry 写了一个单例注册表:

 复制代码class registry {
public:
    static registry& instance() {
        static registry r;            // C++11 保证线程安全
        return r;
    }    void register_logger(shared_ptr log) {
        lock_guard lock(mtx_);
        loggers_[log->name()] = move(log);
    }    shared_ptr get(const string& name) {
        lock_guard lock(mtx_);
        auto it = loggers_.find(name);
        return it != loggers_.end() ? it->second : nullptr;
    }
};

macros.h — 级别过滤宏

 复制代码#define LOG_TRACE(logger, msg) if ((logger)->should_log(level::trace)) (logger)->trace(msg)
#define LOG_DEBUG(logger, msg) if ((logger)->should_log(level::debug)) (logger)->debug(msg)
#define LOG_INFO(logger, msg)  if ((logger)->should_log(level::info))  (logger)->info(msg)
#define LOG_WARN(logger, msg)  if ((logger)->should_log(level::warn))  (logger)->warn(msg)
#define LOG_ERROR(logger, msg) if ((logger)->should_log(level::err))   (logger)->error(msg)
#define LOG_CRIT(logger, msg)  if ((logger)->should_log(level::crit))  (logger)->crit(msg)

if 在前面——如果级别没达标,连 payload 的字符串拼接都不执行。


Benchmark

 复制代码// 同步单线程
auto log = make_shared("bench", make_shared("bench.log"));
log->set_level(level::info);
for (int i = 0; i < 100000; i++) LOG_INFO(log, "bench message");
 复制代码同步单线程 100K 条: 57ms → 1,754,385 msg/s
同步 4 线程  100K 条:201ms →   497,512 msg/s
异步单线程 100K 条:250ms →   400,000 msg/s
异步 4 线程  100K 条:1470ms →  68,027 msg/s

同步单线程 175 万/秒——基本是 std::string 拼接 + 内存拷贝的上限。异步 4 线程下的锁竞争是下一步优化点(ring buffer / 无锁队列)。


跟 muduo 合体

把这个 logger 跟另一个从零写的 Reactor 网络库串了起来,写了一个多人聊天室:

 复制代码auto console = make_shared();
auto file    = make_shared("chat.log");
auto log     = make_shared("chat", console);
log->add_sink(file);EventLoop loop;
TcpServer server(&loop, 8080);
server.setThreadNum(4);
vector clients;server.setConnectionCallback([&](TcpConnection* conn) {
    clients.push_back(conn);
    LOG_INFO(log, "新客户端连接,在线: " + to_string(clients.size()));
});server.setMessageCallback([&](TcpConnection* conn, const char* data, size_t len) {
    string msg(data, len);
    LOG_INFO(log, "收到: " + msg);
    for (auto* c : clients)
        if (c != conn) c->send(msg + "rn");     // 广播
});server.start();
loop.loop();

多个终端 telnet 到 8080,打字互发消息。chat.log 同步记录每一条:

 复制代码[22:56:52] 聊天服务器启动,端口 8080...
[22:57:03] 新客户端连接,在线:1
[22:57:31] 收到:hello from client 1
[22:57:36] 收到:hello from client 2

两个自己造的轮子第一次协同工作,那种感觉很难描述。以前写出来的东西都是用别人的库——这是第一次自己的两个库在同一个进程里配合,"我写的库能给我的另一个库当基础设施"。


踩过的坑

  1. localtime 不是线程安全的。 C 标准库返回内部缓冲区指针,多线程同时调会互相覆盖。必须用 localtime_r(Linux) 或 localtime_s(Windows),或者自己加锁。
  2. pattern 编译和格式化要分离。 最初在 format() 里边解析 %flag 边拼字符串——每条日志都重新扫描模式串。改成"构造函数编译、运行时遍历对象数组"之后,格式化从 O(n) 字符串扫描变成了 O(n) 数组遍历——常数差距很大。
  3. lock_guard vs unique_lock。 lock_guard 轻量但不可解锁,只用于简单的保护块。unique_lock 可以手动 unlock + relock,配合 condition_variable::wait 必须用它。
  4. 异步析构的顺序不能错。 async_logger 的析构里必须先 q_.stop() 唤醒所有 worker,再 join()。如果反过来——join 之前没 stop——worker 永远阻塞在 dequeue() 上,整个程序 hang 住。

计划

接下来还会更新:

  • color_stdout_sink(按级别着色:error 红、warn 黄)
  • daily_rolling_sink(按天切文件,不是按大小)
  • flush_on_crash(SIGSEGV 时自动刷盘)
  • rate limiter(相同日志 N 秒内只输出一次)
  • benchmark vs spdlog(性能对比表格)
本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
作者最新文章
编程开发
相关文章 更多
精品专题 更多
装机必备

正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

Windows

正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

PDF教程

正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

macOS软件

正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

IOS软件

正软商城iOS软件专区,精选适用于iPhone和iPad的办公、学习、影音、设计、效率及AI应用,提供功能介绍、适用设备、系统要求和正版获取方式等信息。

AI

正软商城AI软件专区,汇集AI写作、AI绘画、AI视频、AI办公、AI编程、AI翻译、智能客服和数据分析等人工智能工具,提供功能介绍、适用平台、收费方式及正版购买信息。

Mac软件 更多
WPS Office macOS版
WPS Office macOS版

WPS Office是一款面向macOS用户的综合办公套件,将文字处理、电子表格、演示文稿、PDF工具、模板与云协作整合在同一应用中。它兼容常见Microsoft Office格式,支持Intel及Apple芯片Mac,基础功能可免费使用,

Shapr3D macOS版
Shapr3D macOS版

Shapr3D是一款面向工业设计、机械工程、建筑概念和三维打印工作流的CAD软件。Mac版采用Parasolid建模内核,支持草图约束、实体建模、工程图、可视化渲染及常见CAD格式交换,并可通过账户在多台设备之间同步项目。

REAPER macOS版
REAPER macOS版

REAPER是Cockos开发的数字音频工作站,提供多轨音频与MIDI录制、剪辑、处理、混音和母带制作工具。Mac版兼容Intel与Apple芯片,支持AU、VST、VST3、CLAP等插件格式,并提供高度可定制的工作流程。

Ableton Live macOS版
Ableton Live macOS版

Ableton Live 是面向音乐制作人与现场表演者的数字音频工作站,提供编曲视图、独具特色的现场视图、音频录制、MIDI创作、实时变速、乐器及效果器。Mac版原生支持Apple芯片,并可连接音频接口、MIDI控制器和第三方插件。

Adobe After Effects macOS版
Adobe After Effects macOS版

Adobe After Effects 是面向动态图形、影视特效与视频合成的专业创作软件。它提供关键帧动画、遮罩抠像、运动跟踪、三维空间、文字动画和表达式等工具,并可与 Premiere Pro、Photoshop、Illustrator

Adobe Audition macOS版
Adobe Audition macOS版

Adobe Audition 是面向播客、影视后期、音乐制作及内容创作者的专业音频工作站,提供波形精修、多轨录制与混音、频谱编辑、降噪修复、响度匹配和效果处理工具,并可与 Adobe Premiere 协同完成视频声音制作。

Adobe Bridge macOS版
Adobe Bridge macOS版

Adobe Bridge 是面向摄影师、设计师及内容团队的免费数字资产管理工具,可集中预览照片、视频和 Adobe 项目文件,并利用评级、标签、关键词、元数据、筛选与收藏集完成分类检索。它还支持批量重命名、格式导出、相机导入及 Camera

Adobe Illustrator macOS版
Adobe Illustrator macOS版

Adobe Illustrator 是 Adobe 推出的专业矢量图形设计软件,适合在 Mac 上制作标志、图标、插画、包装、信息图及印刷版面。它以路径和锚点构建可无损缩放的图形,并提供钢笔、形状、文字、图像描摹、渐变、画板及生成式功能。

Adobe InDesign macOS版
Adobe InDesign macOS版

Adobe InDesign 是面向印刷品与数字出版物的专业版面设计软件,可在 Mac 上制作书籍、杂志、宣传册、海报、报告及交互式文档。它提供精细的文字样式、网格、母版、长文档管理和印前输出能力。

Adobe Lightroom Classic macOS版
Adobe Lightroom Classic macOS版

Adobe Lightroom Classic 是面向桌面摄影工作流的照片管理与后期处理软件,可将原始照片保存在本地硬盘,通过目录、关键词、评分和收藏夹高效整理图库,并提供 RAW 冲印、蒙版、镜头校正、批量同步及多格式导出等功能。

Adobe Media Encoder macOS版
Adobe Media Encoder macOS版

Adobe Media Encoder 是 Adobe 推出的专业音视频编码与转码工具,可通过队列、预设和监视文件夹批量完成格式转换、代理文件创建及多平台成片输出,并与 Premiere Pro、After Effects 等软件紧密协作。

Adobe Photoshop macOS版
Adobe Photoshop macOS版

Adobe Photoshop 是面向摄影师、设计师和内容创作者的专业图像处理软件,提供图层、蒙版、选区、修复、调色、文字排版、智能对象及生成式编辑等能力。Mac 版支持 Intel 与 Apple Silicon 处理器。

WINDOWS 更多
Adobe InDesign Windows版
Adobe InDesign Windows版

Adobe InDesign 是面向印刷与数字出版的专业页面排版工具,可制作书籍、杂志、宣传册、海报、交互式 PDF 和电子书。它提供精细的文字样式、多页面管理、印前检查及多格式输出能力,并能与 Photoshop、Illustrator

Adobe Illustrator Windows版
Adobe Illustrator Windows版

Adobe Illustrator 是用于绘制矢量图形、插画、图标、徽标和版式的专业设计工具。它以路径和锚点为核心,作品放大后仍能保持清晰,并提供画板、文字、渐变、图像描摹及精确变换等功能,适合品牌视觉、印刷物料、数字界面和商业插画创作。

Adobe Bridge Windows版
Adobe Bridge Windows版

Adobe Bridge 是 Adobe 推出的创意资产管理工具,可集中预览、分类和查找照片、视频、PDF及多种设计文件,并支持元数据、关键词、评级、批量重命名和格式导出。它可与 Photoshop、Illustrator 等应用衔接,

Affinity Photo Windows版
Affinity Photo Windows版

Affinity Photo 是一款面向摄影师、修图师和视觉设计人员的桌面图像编辑工具,提供 RAW 显影、多图层合成、蒙版、实时滤镜、批处理和色彩管理等功能。原独立产品已停止销售,其照片编辑能力已延续至免费的新 Affinity 应用;

Adobe Lightroom Classic Windows版
Adobe Lightroom Classic Windows版

Adobe Lightroom Classic 是面向 Windows 桌面摄影工作流的照片管理与无损编辑工具,可通过目录集中整理大型图库,并完成 RAW 显影、局部蒙版、批量调整、打印和多格式导出。它强调本地文件管理与精细控制,

Figma Windows版
Figma Windows版

Figma 是面向产品设计与开发团队的云端协作平台,可在 Windows 桌面客户端中完成界面绘制、组件管理、交互原型、评论评审和开发交付。文件保存在云端,多名成员能够同步编辑并共享设计系统;官方同时提供免费 Starter 方案及按席位计

Affinity Designer Windows版
Affinity Designer Windows版

Affinity Designer 是面向矢量图形、数字插画和界面设计的专业创作工具,可在矢量、像素与导出工作区之间快速切换。它提供曲线编辑、形状构建、画板、排版、图层和专业色彩管理,并支持常见设计文件的导入及多种印刷、网页格式输出。

Capture One Pro Windows版
Capture One Pro Windows版

Capture One Pro 是面向摄影师和影像团队的专业桌面照片处理工具,提供相机定制色彩配置、RAW转换、图层与蒙版、联机拍摄、目录及会话管理等功能。它适合棚拍、人像、商业产品、婚礼和风光摄影,

Adobe Acrobat Pro Windows版
Adobe Acrobat Pro Windows版

Adobe Acrobat Pro 是面向专业 PDF 工作的综合解决方案,可在 Windows 中创建、编辑、转换、比较、整理和保护文档,并支持表单、电子签名、扫描件文字识别及敏感信息永久遮盖。订阅还包含网页、移动服务和云端协作能力,

Adobe After Effects Windows版
Adobe After Effects Windows版

Adobe After Effects 是面向动态图形、动画与影视视觉特效制作的专业工具,可完成关键帧动画、图层合成、遮罩跟踪、抠像、文字动画和二维三维元素整合。Windows 版通过 Adobe Creative Cloud 安装及更新,

DaVinci Resolve Windows版
DaVinci Resolve Windows版

DaVinci Resolve是Blackmagic Design推出的一体化影视后期工具,将素材管理、非线性剪辑、专业调色、Fusion视觉特效、Fairlight音频处理和成片交付整合在统一工程中。免费版已覆盖多数个人创作需求,

Adobe Media Encoder Windows版
Adobe Media Encoder Windows版

Adobe Media Encoder 是 Adobe 面向视频与音频制作流程提供的专业编码和转码工具,可批量处理媒体文件、创建代理、套用输出预设并监控文件夹自动执行任务。它与 Premiere、After Effects、Audition