From 8486d3fee8a001a7dd6ea74f671b0ed646c2dfd9 Mon Sep 17 00:00:00 2001 From: luying Date: Mon, 23 May 2022 21:19:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=95=B0=E6=95=B0=EF=BC=9A=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E7=94=A8pinus-logger=E6=9B=BF=E4=BB=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game-server/app.ts | 3 +- game-server/app/services/sdk/ta.ts | 393 +++++++++++++++++++++++++ game-server/app/services/sdkService.ts | 10 +- game-server/app/util/logger.ts | 20 +- game-server/config/dev/log4js.ts | 21 +- 5 files changed, 431 insertions(+), 16 deletions(-) create mode 100644 game-server/app/services/sdk/ta.ts diff --git a/game-server/app.ts b/game-server/app.ts index edc025465..77bae454c 100644 --- a/game-server/app.ts +++ b/game-server/app.ts @@ -27,7 +27,7 @@ import * as redLockService from './app/services/redLockService'; import _pinus = require('pinus'); import { updateTeamStatus } from './app/services/comBattleService'; import { resResult, genCode } from './app/pubUtils/util'; -import { errlogger, infologger } from './app/util/logger'; +import { errlogger, infologger, loadLogger } from './app/util/logger'; import { connectThinkingData, getTire } from './app/services/sdkService'; import { loadGmDb } from './app/db'; const fs = require('fs'); @@ -73,6 +73,7 @@ app.configure(function () { setupRoutes(app); setupFilters(app); connectThinkingData(app); + loadLogger(app.getServerId()) app.set(RESERVED.ERROR_HANDLER, errorHandler); app.set(RESERVED.GLOBAL_ERROR_HANDLER, globalErrorHandler); diff --git a/game-server/app/services/sdk/ta.ts b/game-server/app/services/sdk/ta.ts new file mode 100644 index 000000000..550207518 --- /dev/null +++ b/game-server/app/services/sdk/ta.ts @@ -0,0 +1,393 @@ +/** + * 模仿thinkingdata-node直接写一个log的写入,而日志直接用pinus-logger实现 + */ +import { taLogger } from "../../util/logger"; + +const KEY_NAME_MATCH_REGEX = /^[a-zA-Z#][a-zA-Z0-9_]+$/; + +let util: any = {}; +util.version = '1.2.2'; +util.each = function (obj, iterator, context) { + if (obj === null) { + return; + } + if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (let i = 0, l = obj.length; i < l; i++) { + if (i in obj && iterator.call(context, obj[i], i, obj) === {}) { + return; + } + } + } else { + for (let key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === {}) { + return; + } + } + } + } +}; +util.formatDate = function (d) { + function pad(n) { + return n < 10 ? '0' + n : n; + } + + function padMilliseconds(n) { + if (n < 10) { + return '00' + n; + } else if (n < 100) { + return '0' + n; + } else { + return n; + } + } + return d.getFullYear() + '-' + + pad(d.getMonth() + 1) + '-' + + pad(d.getDate()) + ' ' + + pad(d.getHours()) + ':' + + pad(d.getMinutes()) + ':' + + pad(d.getSeconds()) + '.' + + padMilliseconds(d.getMilliseconds()); +}; + +util.searchObjDate = function (o) { + if (util.check.isObject(o) || util.check.isArray(o)) { + util.each(o, function (a, b) { + if (util.check.isObject(a) || util.check.isArray(a)) { + util.searchObjDate(o[b]); + } else { + if (util.check.isDate(a)) { + o[b] = util.formatDate(a); + } + } + }); + } +}; + +util.check = { + isUndefined: function (obj) { + return obj === void 0; + }, + + isObject: function (obj) { + return (toString.call(obj) === '[object Object]') && (obj !== null); + }, + + isEmptyObject: function (obj) { + if (util.check.isObject(obj)) { + for (let key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + return false; + } + } + return true; + } + return false; + }, + + isArray: function (obj) { + return toString.call(obj) === '[object Array]'; + }, + + isString: function (obj) { + return toString.call(obj) === '[object String]'; + }, + + isDate: function (obj) { + return toString.call(obj) === '[object Date]'; + }, + + isNumber: function (obj) { + return toString.call(obj) === '[object Number]'; + }, + + isBoolean: function (obj) { + return toString.call(obj) === '[object Boolean]'; + }, + + isJSONString: function (str) { + try { + JSON.parse(str); + } catch (e) { + return false; + } + return true; + } +}; + +util.properties = { + _strip: function (prop) { + if (!util.check.isObject(prop)) { + return prop; + } + util.each(prop, function (v, k) { + if (!(util.check.isString(v) || util.check.isNumber(v) || util.check.isDate(v) || util.check.isBoolean(v) || util.check.isArray(v))) { + util.log('您的数据-', k, v, '-格式不满足要求,我们已经将其删除. 属性值只支持 String, Number, Date, Boolean, Array'); + delete prop[k]; + } + }); + return prop; + }, + + _checkPropertiesKey: function (obj) { + let flag = true; + util.each(obj, (v, k) => { + if (!KEY_NAME_MATCH_REGEX.test(k)) { + util.log('不合法的 KEY 值: ' + k); + flag = false; + } + }); + return flag; + }, + + name: function (s) { + if (!util.check.isString(s) || !KEY_NAME_MATCH_REGEX.test(s)) { + util.log('请检查参数格式, 事件或属性名称必须是英文字母、 \'util\'或\'#\' 开头, 包含字母和数字的字符串: ' + s); + return false; + } else { + return true; + } + }, + + properties: function (p) { + this._strip(p); + if (p) { + if (util.check.isObject(p)) { + if (this._checkPropertiesKey(p)) { + return true; + } else { + util.log('请检查参数格式, properties 的 key 只能以字母或\'#\'开头,包含数字、字母和下划线 util'); + return false; + } + } else { + util.log('properties 可以没有,但有的话必须是对象'); + return false; + } + } else { + return true; + } + }, + + propertiesMust: function (p) { + this._strip(p); + if (p === undefined || !util.check.isObject(p) || util.check.isEmptyObject(p)) { + util.log('properties 必须是对象且有值'); + return false; + } else { + if (this._checkPropertiesKey(p)) { + return true; + } else { + util.log('请检查参数格式, properties 的 key 只能以字母或\'#\'开头,包含数字、字母和下划线 util'); + return false; + } + } + }, + + userId: function (id) { + if (util.check.isString(id) && /^.+$/.test(id)) { + return true; + } else { + util.log('用户 id 必须是不能为空'); + return false; + } + } +} + + +const DEFAULT_PROPERTIES = { + "#lib": "node", + "#lib_version": util.version, +}; + +/** + * 内部函数,创建 ta 实例 + */ +let _createClient = function (consumer) { + let ta: any = {}; + ta.consumer = consumer; + ta.superProperties = {}; + + ta._basicCheck = function (params) { + if (!util.check.isObject(params)) { + return new Error("params for track must be an object"); + } + + if (!util.properties.userId(params.accountId) && !util.properties.userId(params.distinctId)) { + return new Error("account ID and distinct ID cannot be empty at the same time"); + } + + return undefined; + }; + + ta.track = function (params, skipLocalCheck = false) { + let callback = params.callback || function () { + }; + if (!ta._trackCheck(params, skipLocalCheck, callback)) { + return; + } + ta._sendRequest("track", params, callback); + }; + + ta._trackCheck = function (params, skipLocalCheck, callback) { + let err = this._basicCheck(params); + if (err) { + callback(err); + return false; + } + if (!util.properties.name(params.event)) { + callback(new Error("invalid event name")); + return false; + } + if (!skipLocalCheck && !util.properties.properties(params.properties)) { + callback(new Error("invalid properties")); + return false; + } + return true; + }; + + ta.userSet = function (params) { + let callback = params.callback || function () { + }; + + let err = this._basicCheck(params); + if (err) { + callback(err); + return; + } + + if (!util.properties.propertiesMust(params.properties)) { + callback(new Error("invalid properties")); + return; + } + + ta._sendRequest("user_set", params, callback); + }; + + ta.userSetOnce = function (params) { + let callback = params.callback || function () { + }; + + let err = this._basicCheck(params); + if (err) { + callback(err); + return; + } + + if (!util.properties.propertiesMust(params.properties)) { + callback(new Error("invalid properties")); + return; + } + + ta._sendRequest("user_setOnce", params, callback); + }; + + ta.userAdd = function (params) { + let callback = params.callback || function () { + }; + + let err = this._basicCheck(params); + if (err) { + callback(err); + return; + } + + if (!util.properties.propertiesMust(params.properties)) { + callback(new Error("invalid properties")); + return; + } + + ta._sendRequest("user_add", params, callback); + }; + + ta.setDynamicSuperProperties = function (getDynamicProperties, callback) { + let err; + if (typeof getDynamicProperties === "function") { + if (util.properties.properties(getDynamicProperties())) { + ta.getDynamicProperties = getDynamicProperties; + } else { + err = new Error("Invalid return type of getDynamicProperties"); + } + } else { + err = new Error("getDynamicProperties must be a funciton"); + } + + if (callback && err) { + callback(err); + } + }; + + // 内部函数,组织数据格式,并发送给对应的 consumer 去处理 + ta._sendRequest = function (type, eventData, callback) { + let time = util.check.isUndefined(eventData.time) || !util.check.isDate(eventData.time) + ? new Date() + : eventData.time; + + let data = { + "#type": type, + "#time": util.formatDate(time), + }; + + if (eventData.distinctId) { + data["#distinct_id"] = eventData.distinctId; + } + + if (eventData.accountId) { + data["#account_id"] = eventData.accountId; + } + + if (eventData.ip) { + data["#ip"] = eventData.ip; + } + + if (eventData.event) { + data["#event_name"] = eventData.event; + } + + if (eventData.eventId) { + data["#event_id"] = eventData.eventId; + } + + if (eventData.firstCheckId) { + data["#first_check_id"] = eventData.firstCheckId; + } + + if (eventData.uuid) { + data["#uuid"] = eventData.uuid; + } + + if (eventData.appId) { + data["#app_id"] = eventData.appId; + } + + if (type === "track" || + type === "track_update" || + type === "track_overwrite") { + data["properties"] = { ...DEFAULT_PROPERTIES, ...ta.superProperties, ...(ta.getDynamicProperties ? ta.getDynamicProperties() : {}) }; + } else { + data["properties"] = {}; + } + + if ( + util.check.isObject(eventData.properties) && + !util.check.isEmptyObject(eventData.properties) + ) { + data['properties'] = { ...data['properties'], ...eventData.properties || {} } + } + + util.searchObjDate(data); + + this.consumer.add(data, callback); + }; + + return ta; +}; + +export function initTaLoggingMode() { + return _createClient({ + add: (msg: string) => { + taLogger.info(JSON.stringify(msg)); + } + }) +} \ No newline at end of file diff --git a/game-server/app/services/sdkService.ts b/game-server/app/services/sdkService.ts index f24631d32..9e70c6551 100644 --- a/game-server/app/services/sdkService.ts +++ b/game-server/app/services/sdkService.ts @@ -10,7 +10,6 @@ import { request37CheckChat, request37GetWord, request37Post } from "../pubUtils import { GuildModel } from "../db/Guild"; import { getRoleOnlineInfo, updateUserInfo } from "./redisService"; import { Application, pinus } from "pinus"; -import { getGuildChannelSid } from "./chatService"; import { getRandSingleEelm, parseGoodStr, readWordTxt, resResult, writeWordTxt } from "../pubUtils/util"; const ThinkingAnalytics = require("thinkingdata-node"); import Trie from '../pubUtils/trie'; @@ -21,6 +20,7 @@ import { SurveyModel } from "../db/Survery"; import { pushGuildInfoUpdate } from "./guildService"; import { sendMessageToUserWithSuc } from "./pushService"; import { GuildLeader } from "../domain/rank"; +import { initTaLoggingMode } from "./sdk/ta"; // 检查私聊是否合法 @@ -171,9 +171,7 @@ export function connectThinkingData(app: Application) { } else if (THINKING_DATA_MODE == THINKING_DATA_MODE_LIST.BATCH) { ta = ThinkingAnalytics.initWithBatchMode(SDK_TA_CONST.APPID, SDK_TA_CONST.SERVER_URL); } else if (THINKING_DATA_MODE == THINKING_DATA_MODE_LIST.LOGGING) { - ta = ThinkingAnalytics.initWithLoggingMode(SDK_TA_CONST.LOG_PATH, { - pm2: true - }); + ta = initTaLoggingMode(); } ta.setDynamicSuperProperties(() => { return { @@ -245,8 +243,8 @@ export async function reportCreateRoleEventToTa(role: RoleType, ip: string) { export function taflush() { let ta = pinus.app.get('ta'); if(!ta) return; - ta.flush(); - ta.close(); + if(ta.flush) ta.flush(); + if(ta.close) ta.close(); } export async function fetch37Words() { diff --git a/game-server/app/util/logger.ts b/game-server/app/util/logger.ts index 03b882dd0..92090bdcd 100644 --- a/game-server/app/util/logger.ts +++ b/game-server/app/util/logger.ts @@ -1,10 +1,20 @@ -import { getLogger } from 'pinus-logger'; +import { getLogger, Logger } from 'pinus-logger'; + +let logger: Logger; +let infologger: Logger; +let errlogger: Logger; +let taLogger: Logger; + +export function loadLogger(sid: string) { + logger = getLogger(); + infologger = getLogger('info', sid); + errlogger = getLogger('err', sid); + taLogger = getLogger('ta'); +} -let logger = getLogger(); -let infologger = getLogger('info') -let errlogger = getLogger('err'); export { logger, infologger, - errlogger + errlogger, + taLogger } \ No newline at end of file diff --git a/game-server/config/dev/log4js.ts b/game-server/config/dev/log4js.ts index 74a4a1d0b..3e38a7a17 100644 --- a/game-server/config/dev/log4js.ts +++ b/game-server/config/dev/log4js.ts @@ -1,7 +1,8 @@ module.exports = { 'appenders': { 'console': { - 'type': 'console' + 'type': 'console', + 'prefix': '${opts:serverId}' }, 'con-log': { 'type': 'file', @@ -102,6 +103,17 @@ module.exports = { 'type': 'basic' }, 'backups': 5 + }, + 'ta': { + 'type': 'file', + 'filename': '/zyz_logs/ta/log', + 'pattern': 'yyyy-MM-dd-hh', + 'alwaysIncludePattern': true, + 'layout': { + 'type': 'pattern', + 'pattern': '%m', + }, + } }, @@ -153,11 +165,12 @@ module.exports = { 'err': { 'appenders': ['console', 'error'], 'level': 'debug' + }, + 'ta': { + 'appenders': ['console', 'ta'], + 'level': 'info' } - }, - - 'prefix': '${opts:serverId} ', 'replaceConsole': true, 'lineDebug': false, 'errorStack': true