Serverless CN
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Write
        • Owners
        • Signed-in users
        • Everyone
        Owners Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
    • Invite by email
      Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Versions and GitHub Sync Sharing URL Help
Menu
Options
Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Write
Owners
  • Owners
  • Signed-in users
  • Everyone
Owners Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
  • Invite by email
    Invitee
  • Publish Note

    Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

    Your note will be visible on your profile and discoverable by anyone.
    Your note is now live.
    This note is visible on your profile and discoverable online.
    Everyone on the web can find and read all notes of this public team.
    See published notes
    Unpublish note
    Please check the box to agree to the Community Guidelines.
    View profile
    Engagement control
    Commenting
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    • Everyone
    Suggest edit
    Permission
    Disabled Forbidden Owners Signed-in users Everyone
    Enable
    Permission
    • Forbidden
    • Owners
    • Signed-in users
    Emoji Reply
    Enable
    Import from Dropbox Google Drive Gist Clipboard
       owned this note    owned this note      
    Published Linked with GitHub
    Subscribed
    • Any changes
      Be notified of any changes
    • Mention me
      Be notified of mention me
    • Unsubscribe
    Subscribe
    --- tags: feature author: Oliver.W, Meng --- # Event Bus Service > Ticket: https://app.asana.com/0/1200011502754281/1200402117339672/f > Event bus is the services that designed by global to support Metrics, Troubleshooting, Dev Mode, Integration with other monitoring services, custom metrics, log search, enable user write functions to response to runtime events. ## Events Event bus records the events that from components CLI and platform, this events records help to do future analytics. ## Architecture Design ![](https://i.imgur.com/iUBsYGE.png) To accomplete the data collection and generate future event analytics report, we will follow the architecture design and extend the services already exist to reuse the services already exist. * Update and deploy the latest event bus services into Tencent to collect the event data and support future event features (eg: logs, dev mode) * Reuse the event integrations services to clean and transform the event data before send to mixpa * All the raw event data are save in Event Bus, compare to the dynamo DB used in global, we will use redis to save those data. * There will have extra events that to track for China users, and all the events are listed here. ## Events ### Platform Events This will tack the events that send from Engine and other serverless platforms, this is used to track the commands or actions executed in serverless platform and its result #### Field details | Property | Datatype | Description | | -------------------------- | --------- | --------------------------------------------------------------------------------------------------- | | `event` | `string*` | Serverless Platform Event type - follows a `domain.object.action` naming convention | | `org_uid` | `string` | ID of org this event falls under | | `user_uid` | `string` | ID of user triggering the event, if applicable for event type | | `client_uid` | `string` | ID of the client. Genereated when first time init | | `created` | `number*` | Event creation time in epoch milliseconds | | `data` | `object*` | Event payload | | `data.object` | `object` | Serverless Platform Object targetted by event | | `data.previous_attributes` | `object` | Only set on `*.updated` event types and contains the properties with values as of before the update | - [x] service.action.succeeded: Emitted when a Service successfully finishes a deploy or remove and etc. | Property | Datatype | Description | | ------------------- | -------- | ------------------------------------------------------------------------------------- | | `org_name` | `string` | The name of the organization. | | `user_uid` | `string` | The UID of the user who performed the Action. | | `action` | `string` | The name of the Action run, like `deploy`, `remove`, `metrics`, etc. | | `app_name` | `string` | The Application name that contains the Service that the Action was performed against. | | `service_name` | `string` | The name of the Service that the Action was performed against. | | `stage_name` | `string` | The name of the Stage of the Service that the Action was performed against. | | `component_name` | `string` | The name of the Component that is being used by the Service. | | `component_version` | `string` | The version of the Component that is being used by the Service. | ```json= { "event": "service.action.succeeded", "org_uid": "1302363968", "user_uid": "1302363968", "data": { "object": { "created": 1601308487731, "org_name": "serverlessinc", "user_uid": "1302363968", "action": "deploy", "app_name": "scf-77e121db", "service_name": "api", "stage_name": "dev", "component_name": "express", "component_version": "2.1.4" }, } } ``` - [x] service.action.failed: Emitted when a Service successfully finishes a deploy or remove and etc. | Property | Datatype | Description | | ------------------- | -------- | ------------------------------------------------------------------------------------- | | `org_name` | `string` | The name of the organization. | | `user_uid` | `string` | The UID of the user who performed the Action. | | `app_name` | `string` | The Application name that contains the Service that the Action was performed against. | | `action` | `string` | The name of the Action run, like `deploy`, `remove`, `metrics`, etc. | | `service_name` | `string` | The name of the Service that the Action was performed against. | | `stage_name` | `string` | The name of the Stage of the Service that the Action was performed against. | | `component_name` | `string` | The name of the Component that is being used by the Service. | | `component_version` | `string` | The version of the Component that is being used by the Service. | | `error_type` | `string` | A categorization of the error to help understand it, like 'action_failed'. | | `error_message` | `string` | The error message. | ```json= { "event": "service.action.failed", "org_uid": "1302363968", "user_uid": "1302363968", "created": 1601308487731, "data": { "object": { "org_name": "serverlessinc", "user_uid": "Lcd7gvBhCnTGzHkP3V", "action": "deploy", "app_name": "fullstack-app", "service_name": "api", "stage_name": "dev", "component_name": "express", "component_version": "2.1.4", "error_type": "action_failed", "error_message": "credentials not found", }, } } ``` - [ ] service.deleted: Emitted when a Service record is removed. | Property | Datatype | Description | | -------------- | -------- | ------------------------------------------------------------------------ | | `object` | `string` | `service` | | `user_uid` | `string` | The UID of the user who deleted the Service. | | `app_name` | `string` | The Application UID that contains the Service that was deleted | | `app_uid` | `string` | The Application UID that contains the Service that was deleted. | | `service_name` | `string` | The name of the Service that was deleted. | | `org_uid` | `string` | The uid of the Organization which contains the service that was deleted. | ```json= { "event": "service.deleted", "org_uid": "1302363968", "user_uid": "1302363968", "created": 1601308487731, "data": { "object": { "org_uid": "Lcd7gvBhCnTGzHkP3V", "user_uid": "Lcd7gvBhCnTGzHkP3V", "app_name": "fullstack-app", "app_uid": "abcd-1234", "service_name": "api", }, } } ``` > 没有找到global相关事件逻辑. ### Components CLI Events The components CLI will send event of each command that execute by users, and all the commands will send to the metrics service | Property | Datatype | Description | | | ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --- | | `timezone` | `string` | Local timezone of user performing the deployment. It uses IANA names, e.g. `Europe/Warsaw` or `America/New_York`. It is available only if `serverless_version` is higher or equal to `2.30.0` or higher or equal to `1.83.3` (for 1.x versions) | | | `components_cli_version` | `string` | Version of Components CLI. | | | `components` | `array` | The arraylist of components used in the project. | | | `provider_name` | `string` | Name of the provider used for deployment. | | | `provider_stage` | `string` | Stage used for deployment. | | | `provider_runtimes` | `array` | Array of runtimes used during deployment. In most cases it will have only one item. Since `2.34.0` version of Serverless Framework, it can also include meta-runtime `$containerimage`, which is set for functions that use `image` instead of `handler` (they use Docker containers). | | | `provider_region` | `string` | Name of the region used for deployment. | | | `user_uid` | `string` | The appid of user | | | `client_uid` | `string` | The unique client id generated | | | `functions_count` | `number` | Number of functions from service configuration used during deployment | | | `events_count` | `number` | Number of events from service configuration used during deployment | | | `event_types` | `array` | Array of unique event types used during deployment | | | `outcome` | `string` | Signifies if command failed or succeded. Can have `success` or `failure` value. | | | `failure_reason` | `string` | error messgae | | | `failure_step` | `string` | The step of error occurs | | | `failure_source` | `string` | The source of error occurs | | | `failure_code` | `string` | The code of errors | | | `requestId` | `string` | The request id of the error request, it's helpful in SCF log | | | `traceId` | `string` | The trace id of the error request, it's helpful in our kibana log system | | | `ci_name` | `string` | Name of the CI engine used for deployment. If it's equal to `untracked`, it means that for that event we were not detecting CI engine at all. In most cases, `ci_name` equal to `untracked` will mean that the deployment wasn't executed in CI environment as previously (versions earlier than `2.30.0` and earlier than `1.83.3` for 1.x versions) we were discarding events coming from CI. If `ci_name` is equal to `undefined`, it means that based on the library that we're using for CI detection, the deployment was not run in CI engine. If `ci_name` is equal to `unknown`, it means that the deployment was executed in CI engine environment, but we weren't able to detect the name of tha specific CI engine. In all other cases, `ci_name` will be equal to CI engine name such as `Circle CI` or `Github Actions` | | - [x] init ```json= { "event":"components.command.init", "org_uid": "Lcd7gvBhCnTGzHkP3V", "user_uid": "z082jZGnkYfxNRNtl0", "client_uid": "uuid", "created": 1601308487731, "data":{ "object":{ "provider_name": "aws", "provider_stage": "prod", "provider_region": "us-east-1", "provider_runtimes": ["python3.8"], "functions_count": 5, "events_count": 5, "event_types": ["http", "sqs"], "npm_dependencies": ["lodash", "middy"], "ci_name": "Github Actions", "timezone": "Europe/Warsaw", "user_id": "m62vrWszWT2TF51111", "user_uid": "m62vrWszWT2TF51111", "step": "获取组件信息", "code": "API_FAILED", "source": "Serverless::Registry", "referral": "www.serverless.com", "requestID": "jid83msdfs-38fdsd-k4j8", "traceId": "zld8-dskj-82ffdsi" } } } ``` - [x] dev ```json= { "eventType":"components.command.dev", "action":"dev", "data":{ "object":{ "action":"dev", "org_name":"1302363968", "user_uid":1302363968, "app_name":"scf-77e121db", "service_name":"scf-nodejs", "stage_name":"dev", "component_name":"scf", "component_version":"0.2.0" }, "created":1623219828936 } } ``` - [x] logs ```json= { "event":"components.command.logs", "action":"logs", "data":{ "object":{ "action":"invoke", "user_uid":"1302363968", "app_name":"scf-77e121db", "stage_name":"dev", // region "component_name":"scf", "error_type":"action_error", "error_messgae":"[FAAS] 无法获取函数 CLS 配置,请检查函数是否配置 CLS 功能" }, "created":1623222952314 } } ``` - [x] invoke ```json= { "event":"components.command.invoke", "action":"invoke", "data":{ "object":{ "action":"invoke", "user_uid":"1302363968", "app_name":"scf-77e121db", "stage_name":"dev", // function // region "component_name":"scf" }, "created":1623222329967 } } ``` - [x] invoke local ```json= { "eventType":"components.command.invoke_local", "action":"invoke_local", "data":{ "object":{ "action":"invoke_local", "app_name":"scf-77e121db", "service_name":"scf-nodejs", "stage_name":"dev", "component_name":"scf" }, "created":1623376745462 } } ``` - [x] deploy ```json= { "event":"components.command.deploy", "action":"deploy", "data":{ "object":{ "org_name":"1302363968", "user_uid":1302363968, "action":"deploy", "app_name":"scf-77e121db", "service_name":"scf-nodejs", "stage_name":"dev", "component_name":"scf", "component_version":"0.2.0" }, "created":1623219820451 } } ``` - [x] remove ```json= { "event":"components.command.remove", "action":"remove", "data":{ "object":{ "org_name":"1302363968", "user_uid":1302363968, "action":"remove", "app_name":"scf-77e121db", "service_name":"scf-nodejs", "stage_name":"dev", "component_name":"scf", "component_version":"0.2.0" }, "created":1623219820451 } } ``` #### Tracked Events --- - [ ] `user.created` : Emitted when a new user is created.https://github.com/serverlessinc/platform-dashboard/blob/438bc1fad85d509de2724750a482755bbf897147/src/webhooks/constants.js#L4 这里面定义了一些events, **user.created** 这个event也是在dashboard中才使用 > This is controlled by Tencent Cloud, we cant easily track this and tell when a new user created. but we can get the new user data by run analytic script. * 复杂, 不考虑腾讯通知方式, 当前中国版本无法得知一个用户是否是新的用户,除非查数据库然后判断数据库中是否有此用户记录,然后判断是否是新用户。此操作代价较大,不可行。 * (用户) 潜在用户(下载) - 创建 - 部署 - 成功 - 2次部署(人周部署次数) - 5次部署 * (用户) 潜在用户(下载) - 当周部署的 - 第二周部署的 * CLI 下载之后, 生成UUID. 并记录在全局配置中 (mac) * 在进行非登陆的操作时候(提交uuid), 默认记录为 -》 匿名用户 - 交互时间 * 在部署的时候(提交uuid, appid) - 创建时间(已有的用户使用最早的时间作为创建时间,否则使用当前时间) 1. 下载CLI global 变量 记录 uuid 2. 非登陆操作 提交uuid, 只存储uuid,以及第一次时间. 下载时间. * uuid - timestamp. (uuid已经存在, 不更新.) ``` { 'user.downloaded: { [uuid]: { downloadAt: 111, } } } ``` 3. 登陆操作 关联appid, 用户创建时间. ``` { 'user.downloaded: { [uuid1|appid]: { createdAt: timestamp downloadAt: 111, } [uuid2|appid]: { createdAt: timestamp downloadAt: 222, } } } ``` 4. { 'user.downloaded: { [uuid]: { appid: xxx, createdAt: 1111, downloadAt: 111, } } } - [ ] `interact.action.succeeded` : Emitted when a user successfully runs an 'action' in the interact feature.https://github.com/serverlessinc/platform-dashboard/blob/438bc1fad85d509de2724750a482755bbf897147/src/studio/components/Invoker.js#L349 > we dont need to track this event at this moment - [ ] `interact.action.failed` : Emitted when a user runs an 'action' in the interact feature and it results in an error.https://github.com/serverlessinc/platform-dashboard/blob/438bc1fad85d509de2724750a482755bbf897147/src/studio/components/Invoker.js#L368 > we dont need to track this event at this moment - [x] `service.action.succeeded` : Emitted when a Service successfully finishes a deploy or remove. - [x] `service.action.failed` : Emitted when a Service fails to finish a deploy or remove. ## 设计文档 ### DB object ```json { [EventType]: { [Action]: EventData[] } } e.g: { 'service.action.succeeded': { deploy: [ { object: { org_name: xxx, // orgName, 此字段无用处,如果用户不指定,默认为appid user_uid: xxx, // 用户客户端唯一id user_id: xxx, // 用户appid action: deploy | remove, // 用户执行的动作 app_name: xxxx, // appName service_name: xxx, // instanceName stage_name: xxx, // stageName component_name: xxx, // component name component_version: 1.0.0, // component version }, created: 1111, //timestamp } ] }, 'service.action.failed': { deploy: [ { object: { org_name: xxx, // orgName, 此字段无用处,如果用户不指定,默认为appid user_uid: xxx, // 用户appid action: deploy |remove, // 用户执行的动作 app_name: xxxx, // appName service_name: xxx, // instanceName stage_name: xxx, // stageName component_name: xxx, // component name component_version: '1.0.0', // component version error_type: xxxx, // 错误的类型 error_message: xxxx, // 错误详情 }, created: 1111, //timestamp } ] } } ``` ### 自定义通知数据结构 EventData ```json { event: 'service.action.(succeeded/failed)', action: xxx, //用户的行为 data: { created: 111, //timestampe object: { action: deploy / remove / etc..., // 用户的行为 [key]: value // 每个行为对应的数据,不固定 } } } ``` ### 自定义事件 : 客户端发送通知统计自定义事件: ### Event bus service Apis 1. dev 环境 2. prod 环境 ### API https://docs.qq.com/doc/DRlFuQ0RERGtRdGd0 --- ## Debug 1. CLI 建立websocket通道: https://github.com/serverless/components/blob/b3a14f4fb9ffe31f54b1c34463370e813a276042/src/cli/commands-cn/run.js#L96 2. registry/handler 中对console进行劫持,发送到event 服务中:https://github.com/serverlessinc/platform-cn/blob/8e68bb450a91540acd7d2181daf44962c569106b/sp-registry/layer/src/handler.js#L411 3. Event 服务把接受到的console发来的消息,通过websocket再返回到cli: https://github.com/serverlessinc/platform-cn/blob/8e68bb450a91540acd7d2181daf44962c569106b/sp-events/code/src/handler.js#L81 ## Dev Mode Current we implement the dev mode based on the socket abilities from Tencent SCF. ### 实现模式 1. dev.js 中,通过部署代码之后的返回结果,调用腾讯云的日志接口,建立websocket通道,接受实时日志: https://github.com/serverless/components/blob/b3a14f4fb9ffe31f54b1c34463370e813a276042/src/cli/commands-cn/dev.js#L271 2. 第一步调用的实际上是sdk中封装的腾讯云的日志接口: 1. https://github.com/serverlessinc/platform-cn/blob/8e68bb450a91540acd7d2181daf44962c569106b/sdk/src/utils.js#L572 实例化腾讯云日志实例 2. 调用腾讯云debug实例中的接口: https://github.com/serverlessinc/platform-cn/blob/8e68bb450a91540acd7d2181daf44962c569106b/sdk/src/utils.js#L574, 建立了ws 连接,由腾讯云日志接口负责实时日志推送 > Based on the events list https://github.com/serverlessinc/company/tree/main/docs/analytics#serverless-platform-events --- ## 相关实现逻辑 ### Components CLI 1. 生成客户端 client_uid(~/.serverless/tencent/client_uid-credentials) 1. 如果当前没有 **client_uid-credentials** 文件,会先根据 uuidv1生成一个对应的值和一个当前的时间,作为此 **client_uid** 生成的时间 ``` [uuid] value=e2d6eda0-d2fc-11eb-b6c7-19e7af884fd1 downloadAt=1624326914426 ``` 2. 生成之后,将生成的**client_uid** 存到云数据库redis 中,格式: ```json { "client:e2d6eda0-d2fc-11eb-b6c7-19e7af884fd1": { "client_uid":{"value":"e2d6eda0-d2fc-11eb-b6c7-19e7af884fd1","downloadAt":1624326914426} } } ``` 2. CLI 命令收集metrics 数据 1. 生成 metrics paylaod: `generatePayload` 2. 相关命令执行,并且根据命令的执行结果,修改 **payload** 的 **outcome**, 同时存储到本地文件中 **~/.serverless/tencent/telemetry-cache** 3. 对于 **deploy** 命令,有以下特殊逻辑: 1. 检查 **client_uid-credentials** 文件,检查当前 **appId** 是否存在,如果不存在,那么加上此 **appId**, 数据格式: ``` [uuid] value=e2d6eda0-d2fc-11eb-b6c7-19e7af884fd1 downloadAt=1624326914426 1302363968=true ``` 3. 同时传递**client_uid**到deploy参数中:**options.client_uid=xxxxx**. **engine/run.js** 检查到有此参数的话,会统计此 **appId** 的最早的实例,记录 **创建时间(createdAt)** 到 **client_uid** 的redis字段中, 这样就把appid和client_uid关联起来。 格式: ```json { "client:e2d6eda0-d2fc-11eb-b6c7-19e7af884fd1": { "client_uid":{"value":"e2d6eda0-d2fc-11eb-b6c7-19e7af884fd1","downloadAt":1624326914426}, "1302363968(appId)": 1624331617147(createdAt), } } ``` 3. **deploy** 命令会把当前所有存在本地文件中 **~/.serverless/tencent/telemetry-cache** 的数据发送到 **Metircs** 服务器,有 服务端进行数据的整合和转发, 发送成功的会,会删除当前文件下的所有统计数据,清空缓存 --- ## Analytics Redis实例 为了存储统计数据,并且和业务的数据存储分离开,所以在dev和prod分别新建了新的redis实例,用来储存统计数据 1. dev: https://console.intl.cloud.tencent.com/redis/instance/manage/detail?instanceId=crs-ppn9awj1&regionId=4 2. prod: https://console.intl.cloud.tencent.com/redis/instance/manage/detail?instanceId=crs-10ydekbx&regionId=1 3. 在各环境的服务中,会配置如下统计数据库环境变量: ``` REDIS_ANALYTICS_HOST=***** REDIS_ANALYTICS_INSTANCE_ID=****** REDIS_ANALYTICS_PASSWORD=****** REDIS_ANALYTICS_PORT=**** ``` ### 到现在为止: 2021/06, 主要存储了如下类型的数据: 1. "client:*", 这一类是每个用户的**client_uid** 数据, 包含 **匿名用户**(只有一个client_uid)和**关联用户**(用户的appID和client_uid关联起来) 2. "service.action.success/fail": 这是通过 `engine/run.js` 发送到 **events services** 的统计数据,统计用户action的结果和数据字段 3. "component.command.deploy": 这是通过 **components CLI** 进行的数据统计,统计的是命令行的数据,发送到 **metrics services**, 现在只存储 **deploy** 的action到数据库中 --- ## Event-integration server 1. Code repo: https://github.com/serverlessinc/event-integrations-cn 2. Ticket: https://app.asana.com/0/1200011502754281/1200502082991966/f 3. Dev SCF: https://console.intl.cloud.tencent.com/scf/list-detail?rid=4&ns=default&id=event-integration-cn-dev 4. Prod SCF: https://console.intl.cloud.tencent.com/scf/list-detail?rid=1&ns=default&id=event-integration-cn-prod&tab=codeTab

    Import from clipboard

    Paste your markdown or webpage here...

    Advanced permission required

    Your current role can only read. Ask the system administrator to acquire write and comment permission.

    This team is disabled

    Sorry, this team is disabled. You can't edit this note.

    This note is locked

    Sorry, only owner can edit this note.

    Reach the limit

    Sorry, you've reached the max length this note can be.
    Please reduce the content or divide it to more notes, thank you!

    Import from Gist

    Import from Snippet

    or

    Export to Snippet

    Are you sure?

    Do you really want to delete this note?
    All users will lose their connection.

    Create a note from template

    Create a note from template

    Oops...
    This template has been removed or transferred.
    Upgrade
    All
    • All
    • Team
    No template.

    Create a template

    Upgrade

    Delete template

    Do you really want to delete this template?
    Turn this template into a regular note and keep its content, versions, and comments.

    This page need refresh

    You have an incompatible client version.
    Refresh to update.
    New version available!
    See releases notes here
    Refresh to enjoy new features.
    Your user state has changed.
    Refresh to load new user state.

    Sign in

    Forgot password

    or

    By clicking below, you agree to our terms of service.

    Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
    Wallet ( )
    Connect another wallet

    New to HackMD? Sign up

    Help

    • English
    • 中文
    • Français
    • Deutsch
    • 日本語
    • Español
    • Català
    • Ελληνικά
    • Português
    • italiano
    • Türkçe
    • Русский
    • Nederlands
    • hrvatski jezik
    • język polski
    • Українська
    • हिन्दी
    • svenska
    • Esperanto
    • dansk

    Documents

    Help & Tutorial

    How to use Book mode

    Slide Example

    API Docs

    Edit in VSCode

    Install browser extension

    Contacts

    Feedback

    Discord

    Send us email

    Resources

    Releases

    Pricing

    Blog

    Policy

    Terms

    Privacy

    Cheatsheet

    Syntax Example Reference
    # Header Header 基本排版
    - Unordered List
    • Unordered List
    1. Ordered List
    1. Ordered List
    - [ ] Todo List
    • Todo List
    > Blockquote
    Blockquote
    **Bold font** Bold font
    *Italics font* Italics font
    ~~Strikethrough~~ Strikethrough
    19^th^ 19th
    H~2~O H2O
    ++Inserted text++ Inserted text
    ==Marked text== Marked text
    [link text](https:// "title") Link
    ![image alt](https:// "title") Image
    `Code` Code 在筆記中貼入程式碼
    ```javascript
    var i = 0;
    ```
    var i = 0;
    :smile: :smile: Emoji list
    {%youtube youtube_id %} Externals
    $L^aT_eX$ LaTeX
    :::info
    This is a alert area.
    :::

    This is a alert area.

    Versions and GitHub Sync
    Get Full History Access

    • Edit version name
    • Delete

    revision author avatar     named on  

    More Less

    Note content is identical to the latest version.
    Compare
      Choose a version
      No search result
      Version not found
    Sign in to link this note to GitHub
    Learn more
    This note is not linked with GitHub
     

    Feedback

    Submission failed, please try again

    Thanks for your support.

    On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

    Please give us some advice and help us improve HackMD.

     

    Thanks for your feedback

    Remove version name

    Do you want to remove this version name and description?

    Transfer ownership

    Transfer to
      Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

        Link with GitHub

        Please authorize HackMD on GitHub
        • Please sign in to GitHub and install the HackMD app on your GitHub repo.
        • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
        Learn more  Sign in to GitHub

        Push the note to GitHub Push to GitHub Pull a file from GitHub

          Authorize again
         

        Choose which file to push to

        Select repo
        Refresh Authorize more repos
        Select branch
        Select file
        Select branch
        Choose version(s) to push
        • Save a new version and push
        • Choose from existing versions
        Include title and tags
        Available push count

        Pull from GitHub

         
        File from GitHub
        File from HackMD

        GitHub Link Settings

        File linked

        Linked by
        File path
        Last synced branch
        Available push count

        Danger Zone

        Unlink
        You will no longer receive notification when GitHub file changes after unlink.

        Syncing

        Push failed

        Push successfully