搭建Android日志系统 美团点评大前端Logan入门指南

2023-05-16

目录

前言

总览

快速开始

下载官方源码

使用IDEA打开Logan Server项目

使用Docker下载MySQL镜像并启动容器

运行Logan Server

修改db.properties

修改log4j.properties

配置Maven使用Tomcat运行程序

访问Logan Server

运行Logan Site

使用Visual Code打开LoganSite

环境要求

安装

运行Logan-Android Sample

部分Github Issue解决方法

请求出错Network Error #323。

LoganSite 接入时文件加载404 #219。

Server SDK 有更详细的教程吗? #315


前言

本文搬运自笔者的语雀博客,因前几天发现有网友点赞,想来还是有些帮助的,故分享出来,毕竟CSDN的SEO做的可太好了。

Logan 是美团点评集团推出的大前端日志系统。名称是 Log 和 An 的组合,代表个体日志服务,同时也是金刚狼大叔的大名。

总览

Logan 开源的是一整套日志体系,包括日志的收集存储,上报分析以及可视化展示。我们提供了五个组件,包括端上日志收集存储 、iOS SDK、Android SDK、Web SDK,后端日志存储分析 Server,日志分析平台 LoganSite。并且提供了一个 Flutter 插件Flutter 插件。

整体架构

快速开始

这里并不是官方文档的搬运,而是手把手教学,把日志系统搭建起来运行,只是看文档的话,确实会有些步骤卡住,不能解决。本文算是对官方文档的一个补充,也是对Github Issues一些问题的回答。

工欲善其事,必先利其器。这里需要小伙伴们先准备好以下工具:Linux开发环境(Window系统也行),Npm,Node.js, Yarn,Java8,IDEA ,Docker,AndroidStudio,Android手机一台。

好的,假装你准备好了,继续往下看。

下载官方源码

第一步,当然是下载源码了。如果使用Github不顺畅,可以参考以下方式:

推荐 SwitchHosts 工具管理 hosts,访问Github更方便。

使用方法,添加一个Hosts,参考下方内容填写:

Title: 随意

Type: Remote

URL: https://raw.hellogithub.com/hosts

Auto Refresh: 最好选 1 hour

工具来源:

https://github.com/521xueweihan/GitHub520

https://github.com/oldj/SwitchHosts

我这里把源码下载本地目录:/work/LogCenter/,解压,同时我还新建两个目录applogs和MySql等下需要用到。

使用IDEA打开Logan Server项目

使用IDEA打开Logan Sever项目备用,目录:/work/LogCenter/Logan/Logan/Server。

如果看官方文档,这里官方只给了创建表的sql语句,我表示很方,由于需要使用到MySQL数据库,所以这里介绍使用Docker搭建MySQL环境,不感兴趣的小伙伴请跳过此章节。

使用Docker下载MySQL镜像并启动容器

Docker怎么安装不用我说了吧。

链接走起自己看:Docker 教程 | 菜鸟教程

打开命令行终端,输入:docker pull mysql:5.7,回车执行,如下:


pc:~$ docker pull mysql:5.7
5.7: Pulling from library/mysql
33847f680f63: Already exists 
5cb67864e624: Already exists 
1a2b594783f5: Already exists 
b30e406dd925: Already exists 
48901e306e4c: Already exists 
603d2b7147fd: Already exists 
802aa684c1c4: Already exists 
5b5a19178915: Pull complete 
f9ce7411c6e4: Pull complete 
f51f6977d9b2: Pull complete 
aeb6b16ce012: Pull complete 
Digest: sha256:be70d18aedc37927293e7947c8de41ae6490ecd4c79df1db40d1b5b5af7d9596
Status: Downloaded newer image for mysql:5.7
docker.io/library/mysql:5.7
  

等待镜像下载完成。输入:docker images,查看镜像如下:


pc:~$ docker images
REPOSITORY                                      TAG                  IMAGE ID            CREATED             SIZE
kibana                                          elastdocker-7.12.0   5ffdef382e6f        2 days ago          1.05GB
logstash                                        elastdocker-7.12.0   97fe3f5ae942        2 days ago          971MB
elasticsearch                                   elastdocker-7.12.0   1ad2d4e4b508        2 days ago          830MB
elastic_certs                                   latest               1ad2d4e4b508        2 days ago          830MB
elastic_keystore                                latest               1ad2d4e4b508        2 days ago          830MB
docker.elastic.co/beats/metricbeat              7.14.0-arm64         9dc075992137        13 days ago         1.19GB
docker.elastic.co/beats/metricbeat              7.14.0               e3583bac930e        13 days ago         517MB
mysql                                           5.7                  8cf625070931        3 weeks ago         448MB
mysql                                           latest               c60d96bd2b77        3 weeks ago         514MB
docker.elastic.co/kibana/kibana                 7.12.0               7a6b1047dd48        4 months ago        1.05GB
docker.elastic.co/elasticsearch/elasticsearch   7.12.0               9337ed510a0c        4 months ago        830MB
docker.elastic.co/logstash/logstash             7.12.0               c283394286f5        4 months ago        971MB
jenkins                                         v1.0.0               fab8efdd0aef        14 months ago       6.12GB
jenkins                                         test                 1eb52b79643e        14 months ago       978MB
hub.c.163.com/public/ubuntu                     16.04-tools          1196ea15dad6        4 years ago         336MB
  

看到了吗?有一行:mysql 5.7 就是我们刚下载的镜像。接下来,我们编写docker-compose配置用于启动mysql:5.7容器。

在MySql目录新建mysql-5.7.yml,编写内容如下:


version: '2.0'
services:
    mysql:
        container_name: "logan-mysql-5.7"
        environment:
            MYSQL_ROOT_PASSWORD: "123456"
        image: "mysql:5.7"
        restart: always
        ports:
            - 3316:3306
  

都看得懂吧,不懂先学习:Docker Compose | 菜鸟教程

命令行终端进入/home/work/LogCenter/MySql目录

输入命令:docker-compose -f mysql-5.7.yml up -d

/home/work/LogCenter/MySql$ docker-compose -f mysql-5.7.yml up -d
Creating network "mysql_default" with the default driver
Creating logan-mysql-5.7 ... done

输入命令:docker-compose ps或者docker ps可以查看启动的容器,state是Up代表正在运行。

/home/work/LogCenter/MySql$ docker-compose ps
     Name                   Command             State                 Ports              
-----------------------------------------------------------------------------------------
logan-mysql-5.7   docker-entrypoint.sh mysqld   Up      0.0.0.0:3316->3306/tcp, 33060/tcp

这时候使用Navicat或者DBeaver连接测试下,端口号和密码见 mysql-5.7.yml,成功。

有时候不那么幸运。连接不上的原因,往往是密码错误,或者远程登录限制了ip,这时候需要进入MySQL容器内部,连接数据库修改下。

参考:docker部署mysql 实现远程连接的示例代码

登录成功,新建一个数据库,名为logan,执行官方给的sql,创建对应的表。对应的sql语句在这个位置:

到这里,MySQL的开发环境就搭好了。

运行Logan Server

到这一步,终于可以运行了。Logan Server是使用Maven管理的项目,所以导入项目的时候,要选择Maven。

修改db.properties

修改IP为1270.0.1,port为3316,database为logan,账号为root,密码为123456,根据你的msql配置来就行了。


jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3316/logan?characterEncoding=UTF8&allowMultiQueries=true&socketTimeout=60000&autoReconnect=true
jdbc.username=root
jdbc.password=123456  

修改log4j.properties

修改log4j.appender.R.File=/home/lb/work/LogCenter/applogs,也就是前面准备工作,创建的目录。


log4j.rootLogger=ERROR,R,stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n

log4j.appender.R=org.apache.log4j.DailyRollingFileAppender
log4j.appender.R.File=/home/lb/work/LogCenter/applogs/logan.log
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%d-[TS] %p %t %c - %m%n
  

配置Maven使用Tomcat运行程序

这个时候还不能运行程序,需要配置下。

依次点 Add Configuration--> + Maven + OK。

填入Command line填入tomcat:run,点OK即可。

然后就可以点绿色三角形按钮,运行程序了。

我们试下运行,报错了,果然too young too simple。

报了这个错误:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project logan-web: Fatal error compiling

经过一番搜索,说是Java版本不一致导致的:

解决Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.7.0:compile_分享传递价值-CSDN博客

好吧,查看下pom.xml,并没有Java版本的配置。那就从File-->Project Structure进去设置下Project SDK改为1.8的版本。

再次运行,看起来好像成功了。

请记住图中Sever的Url地址: http://localhost:8080/logan-web,就下就用到了。

访问Logan Server

打开浏览器,访问控制台打印的服务器Url:http://localhost:8080/logan-web,竟然报错了,心累有没有。

再看下控制台输出:


[INFO] 
[INFO] --------------------< com.meituan.logan:logan-web >---------------------
[INFO] Building logan-web 1.0-SNAPSHOT
[INFO] --------------------------------[ war ]---------------------------------
[INFO] 
[INFO] >>> tomcat-maven-plugin:1.1:run (default-cli) > compile @ logan-web >>>
[INFO] 
[INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ logan-web ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 7 resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ logan-web ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] <<< tomcat-maven-plugin:1.1:run (default-cli) < compile @ logan-web <<<
[INFO] 
[INFO] 
[INFO] --- tomcat-maven-plugin:1.1:run (default-cli) @ logan-web ---
[INFO] Running war on http://localhost:8080/logan-web
[INFO] Using existing Tomcat server configuration at /home/work/IdeaProjects/Logan-master/Logan/Server/target/tomcat
八月 13, 2021 6:39:29 下午 org.apache.catalina.startup.Embedded start
信息: Starting tomcat server
八月 13, 2021 6:39:29 下午 org.apache.catalina.core.StandardEngine start
信息: Starting Servlet Engine: Apache Tomcat/6.0.29
八月 13, 2021 6:39:30 下午 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring root WebApplicationContext
*********post initializaiton
*********post initializaiton
八月 13, 2021 6:39:32 下午 org.apache.catalina.core.ApplicationContext log
信息: Initializing Spring DispatcherServlet 'mvc-dispatcher'
八月 13, 2021 6:39:32 下午 org.apache.coyote.http11.Http11Protocol init
信息: Initializing Coyote HTTP/1.1 on http-8080
八月 13, 2021 6:39:32 下午 org.apache.coyote.http11.Http11Protocol start
信息: Starting Coyote HTTP/1.1 on http-8080
八月 13, 2021 6:40:28 下午 org.apache.jasper.compiler.JDTCompiler$1 findType
严重: Compilation error
org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException
	at org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader.<init>(ClassFileReader.java:342)
	at org.apache.jasper.compiler.JDTCompiler$1.findType(JDTCompiler.java:206)
	at org.apache.jasper.compiler.JDTCompiler$1.findType(JDTCompiler.java:163)
	at org.eclipse.jdt.internal.compiler.lookup.LookupEnvironment.askForType(LookupEnvironment.java:96)
	at org.eclipse.jdt.internal.compiler.lookup.UnresolvedReferenceBinding.resolve(UnresolvedReferenceBinding.java:49)
	at org.eclipse.jdt.internal.compiler.lookup.BinaryTypeBinding.resolveType(BinaryTypeBinding.java:97)
	at org.eclipse.jdt.internal.compiler.lookup.PackageBinding.getTypeOrPackage(PackageBinding.java:167)
	at org.eclipse.jdt.internal.compiler.lookup.Scope.getType(Scope.java:2187)
	at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.resolve(TypeDeclaration.java:974)
	at org.eclipse.jdt.internal.compiler.ast.TypeDeclaration.resolve(TypeDeclaration.java:1164)
	at org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration.resolve(CompilationUnitDeclaration.java:366)
	at org.eclipse.jdt.internal.compiler.Compiler.process(Compiler.java:623)
	at org.eclipse.jdt.internal.compiler.Compiler.compile(Compiler.java:392)
	at org.apache.jasper.compiler.JDTCompiler.generateClass(JDTCompiler.java:429)
	at org.apache.jasper.compiler.Compiler.compile(Compiler.java:349)
	at org.apache.jasper.compiler.Compiler.compile(Compiler.java:327)
	at org.apache.jasper.compiler.Compiler.compile(Compiler.java:314)
	at org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:592)
	at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:317)
	at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
	at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:260)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at com.meituan.logan.web.filter.CORSFilter.doFilter(CORSFilter.java:47)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857)
	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
	at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
	at java.lang.Thread.run(Thread.java:748)

八月 13, 2021 6:40:28 下午 org.apache.catalina.core.StandardWrapperValve invoke
严重: Servlet.service() for servlet jsp threw exception
org.apache.jasper.JasperException: Unable to compile class for JSP: 

An error occurred at line: 1 in the generated java file
The type java.io.ObjectInputStream cannot be resolved. It is indirectly referenced from required .class files

Stacktrace:
	at org.apache.jasper.compiler.DefaultErrorHandler.javacError(DefaultErrorHandler.java:92)
	at org.apache.jasper.compiler.ErrorDispatcher.javacError(ErrorDispatcher.java:330)
	at org.apache.jasper.compiler.JDTCompiler.generateClass(JDTCompiler.java:439)
	at org.apache.jasper.compiler.Compiler.compile(Compiler.java:349)
	at org.apache.jasper.compiler.Compiler.compile(Compiler.java:327)
	at org.apache.jasper.compiler.Compiler.compile(Compiler.java:314)
	at org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:592)
	at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:317)
	at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:313)
	at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:260)
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at com.meituan.logan.web.filter.CORSFilter.doFilter(CORSFilter.java:47)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:233)
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:298)
	at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:857)
	at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:588)
	at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:489)
	at java.lang.Thread.run(Thread.java:748)

  

很棒,果然不是搞服务器开发的,运行个项目都这么坎坷。

度娘搜索:org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException

说Tomcat版本太低了:

Tomcat 启动异常:org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException

很好,万能的度娘。升级Tomcat版本试下。在pom.xml中配置tomcat7插件。


 <plugin>
        <groupId>org.apache.tomcat.maven</groupId>
        <artifactId>tomcat7-maven-plugin</artifactId>
        <version>2.2</version>
        <configuration>
        <port>8888</port>
        </configuration>
</plugin>  

添加配置后点下Maven同步。

接着编辑Maven的Command Line为:tomcat7:run,点击OK保存。

然后接着运行就可以了。

当然,你可以通过右边的Maven的Tomcat7插件直接运行,随你喜欢。

使用Tomcat7可能会有下面的报错。


八月 13, 2021 6:20:35 下午 org.apache.catalina.core.StandardService startInternal
信息: Starting service Tomcat
八月 13, 2021 6:20:35 下午 org.apache.catalina.core.StandardEngine startInternal
信息: Starting Servlet Engine: Apache Tomcat/7.0.47
八月 13, 2021 6:20:37 下午 org.apache.catalina.startup.ContextConfig processAnnotationsJar
严重: Unable to process Jar entry [module-info.class] from Jar [jar:file:/home/nxg/.m2/repository/com/fasterxml/jackson/core/jackson-annotations/2.10.1/jackson-annotations-2.10.1.jar!/] for annotations
org.apache.tomcat.util.bcel.classfile.ClassFormatException: Invalid byte tag in constant pool: 19
	at org.apache.tomcat.util.bcel.classfile.Constant.readConstant(Constant.java:133)
	at org.apache.tomcat.util.bcel.classfile.ConstantPool.<init>(ConstantPool.java:60)
	at org.apache.tomcat.util.bcel.classfile.ClassParser.readConstantPool(ClassParser.java:209)
	at org.apache.tomcat.util.bcel.classfile.ClassParser.parse(ClassParser.java:119)
	at org.apache.catalina.startup.ContextConfig.processAnnotationsStream(ContextConfig.java:2134)
	at org.apache.catalina.startup.ContextConfig.processAnnotationsJar(ContextConfig.java:2010)
	at org.apache.catalina.startup.ContextConfig.processAnnotationsUrl(ContextConfig.java:1976)
	at org.apache.catalina.startup.ContextConfig.processAnnotations(ContextConfig.java:1961)
	at org.apache.catalina.startup.ContextConfig.webConfig(ContextConfig.java:1319)
	at org.apache.catalina.startup.ContextConfig.configureStart(ContextConfig.java:878)
	at org.apache.catalina.startup.ContextConfig.lifecycleEvent(ContextConfig.java:376)
	at org.apache.catalina.util.LifecycleSupport.fireLifecycleEvent(LifecycleSupport.java:119)
	at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:90)
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5322)
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
	at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)  

百度搜索到相关文章说这个没有影响,实际测试验证这个报错确实没有影响,先跳过。

当你可能还是遇到下面的问题。

提示:Failed to execute goal org.codehaus.mojo:tomcat-maven-plugin:1.1:run (default-cli) on project logan-web: Could not start Tomcat

方法也是尝试使用tomcat7版本运行。

浏览器刷新下网页,输出:hello, logan,感觉棒极了,就是着这种feel。

运行Logan Site

运行完后端,就轮到前端了。准备好Node.js开发环境,Npm,Yarn啥的不能少,这里还是稍微提下一下遇到的坑。

Npm很慢,需要设置淘宝镜像源。教程见:npm更换成淘宝镜像源以及cnpm

设置镜像源也不要使用npm install ,直接用这种方式:

npm config set registry https://registry.npm.taobao.org

使用Visual Code打开LoganSite

使用Visual Code打开LoganSite备用。

环境要求

Node: ^10.15.3 yarn: ^1.15.2 或 npm ^6.12.0

安装

本地运行

在LoganSite根目录下创建文件.env.development,并在其中指定API_BASE_URL环境变量改为:

API_BASE_URL=http://localhost:8080/logan-web

没错,这个url就是运行Logan Server里控制台打印的URL。

然后LoganSite根目录执行以下命令,或者执行cd命令先进入LoganSite根目录。


$ yarn 
$ yarn start  

为啥不推荐用npm,嗯,谁用谁知道。

注意:.env.development一定要放在LoganSite根目录下,填写的url必须是Logan Server里控制台打印的URL。

如果你遇到了跟Github Issue相同的问题:

1.LoganSite启动后提示错误:请求出错Network Error #323。

2.LoganSite启动后提示错误:LoganSite 接入时文件加载404 #219。

请按照上述方式检查修改。

F12打开控制台,点Network选项卡,通常都是:

http://localhost:8080/logan-web/logan/latest.json

这个url请求报错,你看看的情况是不是这个问题。

运行Logan-Android Sample

AndroidStudio直接导入Logan-Android项目,由于依赖了Android-SDK源码,因此编译需要设置下NDK路径,要求NDK版本小于等于16.1.4479499。在Logan-Android根目录下的local.properties加入ndk.dir=/work/android/android-ndk-r15c,根据要求填入你的NDK路径。


## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Wed Aug 11 18:04:17 CST 2021
sdk.dir=/work/android/android-sdk-linux
ndk.dir=/work/android/android-ndk-r15c
  

然后修改test.logan.dianping.com.logan.RealSendLogRunnable这里的url如下图:

完整代码如下:

/*
 * Copyright (c) 2018-present, 美团点评
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package test.logan.dianping.com.logan;

import android.text.TextUtils;
import android.util.Log;

import com.dianping.logan.SendLogRunnable;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.ProtocolException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;

public class RealSendLogRunnable extends SendLogRunnable {

    private static final String TAG = "RealSendLogRunnable";

    private String mUploadLogUrl = "http://192.168.1.139:8080/logan-web/logan/upload.json";

    @Override
    public void sendLog(File logFile) {
        boolean success = doSendFileByAction(logFile);
        Log.d("上传日志测试", "日志上传测试结果:" + success);
        // Must Call finish after send log
        finish();
        if (logFile.getName().contains(".copy")) {
            logFile.delete();
        }
    }

    public void setIp(String ip) {
        mUploadLogUrl = "http://" + ip + ":8080/logan-web/logan/upload.json";
    }

    private HashMap<String, String> getActionHeader() {
        HashMap<String, String> map = new HashMap<>();
        map.put("Content-Type", "binary/octet-stream"); //二进制上传
        map.put("client", "android");
        return map;
    }

    /**
     * 主动上报
     */
    private boolean doSendFileByAction(File logFile) {
        boolean isSuccess = false;
        try {
            FileInputStream fileStream = new FileInputStream(logFile);
            byte[] backData = doPostRequest(mUploadLogUrl, fileStream, getActionHeader());
            isSuccess = handleSendLogBackData(backData);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return isSuccess;
    }

    private byte[] doPostRequest(String url, InputStream inputData, Map<String, String> headerMap) {
        Log.i(TAG, "doPostRequest: url " + url);
        int statusCode = -1;
        byte[] data = null;
        OutputStream outputStream = null;
        InputStream inputStream = null;
        HttpURLConnection c = null;
        ByteArrayOutputStream back;
        byte[] Buffer = new byte[2048];
        try {
            java.net.URL u = new URL(url);
            c = (HttpURLConnection) u.openConnection();
            if (c instanceof HttpsURLConnection) {
                ((HttpsURLConnection) c).setHostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                });
            }
            Set<Map.Entry<String, String>> entrySet = headerMap.entrySet();
            for (Map.Entry<String, String> tempEntry : entrySet) {
                c.addRequestProperty(tempEntry.getKey(), tempEntry.getValue());
            }
            c.setReadTimeout(15000);
            c.setConnectTimeout(15000);
            c.setDoInput(true);
            c.setDoOutput(true);
            c.setRequestMethod("POST");
            outputStream = c.getOutputStream();
            int i;
            while ((i = inputData.read(Buffer)) != -1) {
                outputStream.write(Buffer, 0, i);
            }
            outputStream.flush();
            statusCode = c.getResponseCode();
            if (statusCode == 200) {
                back = new ByteArrayOutputStream();
                inputStream = c.getInputStream();
                while ((i = inputStream.read(Buffer)) != -1) {
                    back.write(Buffer, 0, i);
                }
                data = back.toByteArray();
            }
        } catch (ProtocolException e) {
            e.printStackTrace();
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (inputData != null) {
                try {
                    inputData.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (c != null) {
                c.disconnect();
            }
        }
        Log.d(TAG, "log send completed, http statusCode : " + statusCode);
        return data;
    }

    /**
     * 处理上传日志接口返回的数据
     */
    private boolean handleSendLogBackData(byte[] backData) throws JSONException {
        boolean isSuccess = false;
        if (backData != null) {
            String data = new String(backData);
            if (!TextUtils.isEmpty(data)) {
                JSONObject jsonObj = new JSONObject(data);
                if (jsonObj.optBoolean("success", false)) {
                    isSuccess = true;
                }
            }
        }
        return isSuccess;
    }
}

注意:IP和端口号需要换成对应的Logan Server的IP和端口号,我这里Logan Server控制台打印的是:http://localhost:8080/logan-web,但Logan-Android Sample是运行在其他设备手上的,所以这里的localhost需要改成Logan Server运行环境的实际IP(局域网或者服务器公网IP),并且保证

Logan-Android Sample运行的设备能够访问Logan Server的实际IP。

还有一个地方,也建议修改,test.logan.dianping.com.logan.MainActivity的loganSendByDefault方法,这里填的Logan Server Url是美团点评官方平台的,但是已经无法访问了,换成自己的方便测试。

代码如下:

    private void loganSendByDefault() {
        String buildVersion = "";
        String appVersion = "";
        try {
            PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
            appVersion = pInfo.versionName;
            buildVersion = String.valueOf(pInfo.versionCode);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        //final String url = "https://openlogan.inf.test.sankuai.com/logan/upload.json";
        final String url = "http://192.168.1.139:8080部分/logan-web/logan/upload.json";
        SimpleDateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd");
        final String date = dataFormat.format(new Date(System.currentTimeMillis()));
        Logan.s(url, date, "1", "logan-test-unionid", "deviceId", buildVersion, appVersion, new SendLogCallback() {
            @Override
            public void onLogSendCompleted(int statusCode, byte[] data) {
                final String resultData = data != null ? new String(data) : "";
                Log.d(TAG, "日志上传结果, http状态码: " + statusCode + ", 详细: " + resultData);
            }
        });
    }

准备就绪,运行Logan-Android Sample,再次强调设备必须能够访问Logan Server的实际IP(同一个局域网或者服务器使用公网IP),然后App界面按钮全部点一遍,看到类似以下日志就说明,日志上传成功了。


2021-08-16 11:55:58.202 17982-17998/test.logan.dianping.com.logan D/test.logan.dianping.com.logan.MyApplication: clogan > cmd : clogan_init | code : -1010
2021-08-16 11:55:58.202 17982-17998/test.logan.dianping.com.logan D/LoganThread: Logan write start
2021-08-16 11:55:58.208 17982-17998/test.logan.dianping.com.logan D/test.logan.dianping.com.logan.MyApplication: clogan > cmd : clogan_open | code : -2010
2021-08-16 11:55:58.209 17982-17998/test.logan.dianping.com.logan D/test.logan.dianping.com.logan.MyApplication: clogan > cmd : clogan_write | code : -4010
2021-08-16 11:55:58.209 17982-17998/test.logan.dianping.com.logan D/LoganThread: Logan write start
2021-08-16 11:55:58.209 17982-17998/test.logan.dianping.com.logan D/LoganThread: Logan write start
2021-08-16 11:55:58.309 17982-17999/test.logan.dianping.com.logan I/mali_so: [File] : hardware/arm/maliT760/driver/product/base/src/mali_base_kbase.c; [Line] : 978; [Func] : base_context_deal_with_version_affairs_rk_ext;
    arm_release_ver of this mali_so is 'r14p0-01rel0', rk_so_ver is '5@0'.
2021-08-16 11:55:58.310 17982-17999/test.logan.dianping.com.logan D/mali_so: [File] : hardware/arm/maliT760/driver/product/base/src/mali_base_kbase.c; [Line] : 983; [Func] : base_context_deal_with_version_affairs_rk_ext;
    current process is NOT sf, to bail out.
2021-08-16 11:55:58.317 17982-17999/test.logan.dianping.com.logan I/OpenGLRenderer: Initialized EGL, version 1.4
2021-08-16 11:55:58.317 17982-17999/test.logan.dianping.com.logan D/OpenGLRenderer: Swap behavior 1
2021-08-16 11:55:58.328 17982-17999/test.logan.dianping.com.logan D/mali_winsys: EGLint new_window_surface(egl_winsys_display*, void*, EGLSurface, EGLConfig, egl_winsys_surface**, egl_color_buffer_format*, EGLBoolean) returns 0x3000
2021-08-16 11:56:00.225 17982-18031/test.logan.dianping.com.logan D/test.logan.dianping.com.logan.MainActivity: times : 0
2021-08-16 11:56:00.225 17982-17998/test.logan.dianping.com.logan D/LoganThread: Logan write start
2021-08-16 11:56:00.231 17982-18031/test.logan.dianping.com.logan D/test.logan.dianping.com.logan.MainActivity: times : 1

2021-08-16 11:56:01.339 17982-18035/test.logan.dianping.com.logan I/RealSendLogRunnable: doPostRequest: url http://192.168.1.139:8080/logan-web/logan/upload.json
2021-08-16 11:56:01.342 17982-18035/test.logan.dianping.com.logan D/NetworkSecurityConfig: No Network Security Config specified, using platform default
2021-08-16 11:56:01.608 17982-18035/test.logan.dianping.com.logan D/RealSendLogRunnable: log send completed, http statusCode : 200
2021-08-16 11:56:01.609 17982-18035/test.logan.dianping.com.logan D/上传日志测试: 日志上传测试结果:false
2021-08-16 11:56:01.793 17982-17998/test.logan.dianping.com.logan D/LoganThread: Logan send start
2021-08-16 11:56:01.793 17982-17998/test.logan.dianping.com.logan D/LoganThread: prepare log file
2021-08-16 11:56:01.794 17982-17998/test.logan.dianping.com.logan D/LoganThread: Logan flush start
2021-08-16 11:56:01.799 17982-18035/test.logan.dianping.com.logan I/SendLogDefaultRunnable: doPostRequest: url = http://192.168.1.139:8080/logan-web/logan/upload.json
2021-08-16 11:56:01.848 17982-18035/test.logan.dianping.com.logan D/SendLogDefaultRunnable: log send completed, http statusCode : 200
2021-08-16 11:56:01.848 17982-18035/test.logan.dianping.com.logan D/test.logan.dianping.com.logan.MainActivity: 日志上传结果, http状态码: 200, 详细: {"code":200,"msg":null,"data":"/logan/downing?name=1_deviceId_1629043200000_23eb244a-7a65-4934-9095-26b2f346de43.log"}
  

我们查看数据库验证下:

查到了两条日志记录,再回到http://localhost:3000/,F5刷新,确实看到了两条日志记录。

点击查看日志详情,没毛病。

感谢阅读,Logan手把手入门指南到这结束了,更多问题欢迎留言交流。

部分Github Issue解决方法

请求出错Network Error #323。

解决方法:

在LoganSite根目录下创建文件.env.development,并在其中指定API_BASE_URL环境变量改为:

API_BASE_URL=http://localhost:8080/logan-web

注意,这个API_BASE_URL就是运行Logan Server里控制台打印的URL,如下图;

注意:.env.development一定要放在LoganSite根目录下,并且检查src/common/api.js里的BASE_URL变量是否指向.env.development配置的变量。


const BASE_URL = process.defineEnv.API_BASE_URL;
const API_TIME_OUT = 30000;
let pendingRequests = [];

// axios instance
const instance = axios.create({
  baseURL: BASE_URL,
  timeout: API_TIME_OUT,
  withCredentials: true
});
  

LoganSite 接入时文件加载404 #219。

解决方法:同#323。

Server SDK 有更详细的教程吗? #315

解决方法:查看本文运行Logan Server章节内容点我跳转。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

搭建Android日志系统 美团点评大前端Logan入门指南 的相关文章

  • 搭建网盘工具kk

    1 主要功能 支持用作简单文件列表 xff0c 无需数据库 xff0c 无用户管理 xff0c 分享等功能支持基本网盘功能 xff0c 需要安装MongoDB及Redis xff0c 支持用户注册 xff0c 文件分享 xff0c 用户目录
  • numpy中transpose详解

    transpose用于numpy中高维度数组的轴变换 xff0c 在二维情况下就是通常说的转置 该方法很不好理解 xff0c 本文详细介绍该方法 该方法有两个实现 xff0c 分别是numpy ndarray transpose和numpy
  • Python压缩文件

    1 标准库中的压缩模块 在我们常用的系统windows和Linux系统中有很多支持的压缩包格式 xff0c 包括但不限于以下种类 xff1a rar zip tar xff0c 以下的标准库的作用就是用于压缩解压缩其中一些格式的压缩包 2
  • LE-VINS:固态激光雷达增强的视觉惯性导航系统

    在光照剧烈变化 动态物体 弱纹理等视觉退化场景 xff0c 视觉路标点的深度估计难度急剧增加 xff0c 导致视觉惯性导航系统 VINS 的精度和鲁棒性恶化 采用非重复扫描原理的固态激光雷达 xff0c 为解决视觉路标点深度估计问题带来便利
  • Gradio介绍

    Gradio App 就是给 AI 算法工程师训练的模型赋予分享给大众的能力 从技术侧拆分 xff0c 由三个部分组成 xff1a 前端页面 43 后端接口 43 AI算法模型推理 Gradio 做了一件事情 xff0c 就是将这三个部分封
  • GitHub Copilot 快速入门

    GitHub Copilot 是 AI 结对程序员 可以使用 GitHub Copilot 在编辑器中获取整行或整个函数的建议 1 简介 让我们首先了解一些关于 GitHub Copilot 的内容 这是 GitHub 和 OpenAI 的
  • 电池连接接触电阻的优化研究

    金属与金属接触处的接触电阻主要受以下因素影响 xff1a 表面结构机械负载 表面结构可以通过表面平整度 表面氧化和吸水性进一步描述 电池组中的这些接头将采用焊接或螺栓连接方式 最初最容易想到的是螺栓母线接头 螺栓扭矩可用于估算力 xff0c
  • (二)CAS统一认证——自定义登录(数据库)

    简介 关于CAS的登录流程 xff0c overlay中只是一个简单的用户名密码登录 casuser Mellon xff0c 这个肯定是不能满足日常生产的需求的 xff0c 在日常开发中遇到最通用的情况就是从数据库中进行身份认证密码的比对
  • springcloud

    一 什么是SpringCloud xff1f Author xff1a 呆萌老师 QQ 2398779723 微信 it daimeng Spring Cloud是一个微服务框架 xff0c 相比Dubbo等RPC框架 Spring Clo
  • 计算机操作系统学习之多生产者多消费者问题

    文章目录 一 问题描述二 问题分析1 关系分析2 整理思路3 设置信号量4 具体实现5 补充 一 问题描述 有一个盘子 xff0c 每次只能放一个水果父亲专门往盘子里放苹果 xff0c 母亲专门往盘子里放橘子女儿专门等着吃盘子里的苹果 xf
  • 姿态角与欧拉角的关系

    1 姿态角与欧拉角 姿态角 xff1a 指的是机体坐标系与地理坐标系的夹角 欧拉角 xff1a 绕机体坐标系三个轴旋转的角度 关系 xff1a 绕某种旋转顺序的欧拉角与姿态角相等 xff08 1 xff09 在NED 北东地 坐标系 xff
  • 从入门到进阶,JAVA书籍的最佳阅读顺序!

    本文首发于知乎 xff0c 已获得1000 43 赞和收藏 原文链接 xff1a https www zhihu com question 269505829 answer 1791006152 先介绍下本人的情况 xff0c 希望对大家学
  • 设计数据密集型应用-C5-主从架构及同步延迟问题

    本文是 设计数据密集型应用 第5章学习笔记 什么是Replication Replication是在多台机器上维护的相同的数据 xff0c 即副本 保存副本的原因有以下几种 xff1a 减小延迟 xff1a 使得地理位置上数据离访问者更近
  • 第一条Pulsar消息发送

    什么是Pulsar pulsar是一个多租户 高性能server to srever消息解决方案 xff0c 最初由雅虎开发 xff0c 现在由apache维护 Pulsar的核心特性 xff1a 多集群云原生支持低延迟 良好的伸缩性多语言
  • 2014找工作总结-机会往往留给有准备的人

    转发请注明出处 xff1a 2014找工作总结 机会往往留给有准备的人 计算机专业同学的充电站 CSDN博客 其实我的求职过程在十一之前就已经结束了 xff0c 总体讲比较顺利 参加面试的几家公司基本都拿到了offer xff0c 分别是阿
  • 【数字图像处理】C++读取、旋转和保存bmp图像文件编程实现

    通过我这些天用C 43 43 读写bmp图像的经历 xff0c 摸索再摸索 xff0c 终于对bmp文件的结构 操作有了一定的了解 xff0c 下面就大概介绍bmp图片纯C 43 43 的读取 旋转和保存的实现过程 要用C 43 43 读取
  • 【数字图像处理】直方图均衡化详解及编程实现

    直方图均衡化的英文名称是Histogram Equalization 图像对比度增强的方法可以分成两类 一类是直接对比度增强方法 另一类是间接对比度增强方法 直方图拉伸和直方图均衡化是两种最常见的间接对比度增强方法 直方图拉伸是通过对比度拉
  • 【GPU编程】体绘制传输函数-分类(Volume Rendering Transfer function:Pre- VS Post-Classification)

    在科学可视化中 xff0c 我们所获得的体数据集经常是代表一些光学上的不同物理属性的单值 通常没有可行的方法可以从这样的数据中获得发射和吸收属性 因此用户必须采用某种映射方法给数据值分配光学属性值来决定数据中的不同结构的模样 这离的映射就被
  • 【OpenGL】理解GL_TRIANGLE_STRIP等绘制三角形序列的三种方式

    GL TRIANGLE STRIP绘制三角形方式很多时候令人疑惑 xff0c 在这里对其运作机理进行解释 一般情况下有三种绘制一系列三角形的方式 xff0c 分别是GL TRIANGLES GL TRIANGLE STRIP和GL TRIA
  • OpenJDK与JDK的区别分析

    OpenJDK与JDK的区别分析 一 以下是具体分析 xff1a 使用过LINUX的人都应该知道 xff0c 在大多数LINUX发行版本里 xff0c 内置或者通过软件源安装JDK的话 xff0c 都是安装的OpenJDK xff0c 那么

随机推荐

  • 【C++深入探索】Copy-and-swap idiom详解和实现安全自我赋值

    任何管理某资源的类比如智能指针需要遵循一个规则 xff08 The Rule of Three xff09 xff1a 如果你需要显式地声明一下三者中的一个 xff1a 析构函数 拷贝构造函数或者是拷贝赋值操作符 xff0c 那么你需要显式
  • 【Linux】Vim编辑器-批量注释与反注释

    vim编辑器 批量注释与反注释 在使用vim编写代码的时候 xff0c 经常需要用到批量注释与反注释一段代码 下面简要介绍其操作 方法一 块选择模式 插入注释 xff1a 用v进入virtual模式 用上下键选中需要注释的行数 按Contr
  • 【算法学习】图相关算法编程实现-深度优先遍历和广度优先遍历

    一 图的表示 图G 61 V E 要表示一个图 xff0c 通常有两种方法 xff1a 邻接表和邻接矩阵 两种方法都既可以表示有向图 xff0c 也可以表示无向图 邻接表表示由一个包含 V 个列表的数组组成 xff0c 其中每个列表对应V中
  • 【笔试面试题】腾讯2013实习生面试算法题及参考答案

    总结了一下自己遇到的以及同学遇到的面试算法题 xff0c 是技术二面 有几道题给出了参考答案 xff0c 还有几道没有好的思路 路过的大侠如果有好的思路请留个言交流下呗 1 八数码问题 xff1a 3 3的格子 xff0c 有1 8个数 x
  • 【Linux学习】epoll详解

    什么是 epoll epoll 是什么 xff1f 按照 man 手册的说法 xff1a 是为处理大批量句柄而作了改进的 poll 当然 xff0c 这不是 2 6 内核才有的 xff0c 它是在 2 5 44 内核中被引进的 epoll
  • 算法设计应该依赖抽象而不是业务

    很多时候 xff0c 算法的设计是归属于详细设计阶段的 一些公司甚至都没有设计而直接编码 这些往往导致很多算法的实现都混杂在业务模块中 典型的特点是 xff0c 这些算法会依赖于业务实体的某些属性的实现 举一个简单的例子 xff0c 我曾经
  • 做程序员老婆的幸福

    刚好看网上一个写程序员老公的 说说程序员老公的一些事情 请帮忙分析是不是典型程序员 xff0c 想起自己就是一个程序员 xff0c 也是一个老公 xff0c 却不以为然起来 虽然不以为然 xff0c 却并没有驳斥的任何意思 毕竟很多人都不一
  • 好习惯成就好程序员

    公司搞了一次技术峰会 xff0c 我有机会和大家聊了一下有关如何成功的问题 会上我向大家提出了我的想法 xff0c 好习惯才能成就好程序员 有很多人可能对这句话很不以为然 xff0c 我也不忙辩解 xff0c 先回答我下面的一个问题 现在我
  • 程序江湖:第三章 莫等闲白了少年头

    周五因为羽毛球比赛 xff0c 没有更新 xff0c 今天继续 读者反馈 xff1a 很多读者反馈主人公的名字比较土 呵呵这个没关系 xff0c 可以在整理的时候统一修改 另外有些人表示特别希望看到职场的故事 xff0c 这个放心 本就是这
  • 程序江湖:第二十章 讲标的前一晚上

    说明 xff1a 非常抱歉 xff0c 这周参加了太多的会议 原来写作也是需要心情的 xff0c 当没有心情的时候 xff0c 你都懒得动笔 欧阳明来到云南的最主要的目的 xff0c 是为了应对昆明客户要求的评标 就是客户邀请了几家资质还可
  • springboot项目搭建

    一 Springboot 基本概念 1 1 什么是 springboot Spring Boot是由Pivotal团队提供的全新框架 xff0c 其设计目的是用来简化新Spring应用的初始搭建以及开发过程 该框架使用了特定的方式来进行配置
  • 管理 VS. 面向对象设计

    我是在吃虾的时候 xff0c 突然想起这个关联的 管理 xff0c 往往就是给你一堆事 xff0c 然后再给你一些人 xff0c OK xff0c 你去做吧 这是你的使命 下面我们来做类比吧 首先一个问题 xff0c 你是认为人重要 xff
  • 技术管理案例:代码规范还要继续推行吗?

    这是实际工作中总结的一个典型案例 是真实的 写出来供大家参考 案例 xff1a 技术经理 Y 今年新到了一个产品部门 xff0c 发现原来产品的代码很乱 xff0c 遗留的问题很多 而现有的人员 xff0c 又大半是新招的 xff0c 很多
  • 苹果成功的根本:统筹创新

    这周的某天中午 xff0c 我们聊起了这个话题 xff0c 是关于苹果为什么成功的话题 很多人都说是因为苹果的创新能力很强 但是诺基亚也不缺乏创新啊 xff01 为什么苹果就能胜出呢 xff1f 我个人认为这有很多方面是因为乔布斯 xff0
  • 建立健康的职业发展观

    Google的Reader要下线了 xff0c 很多人都说这是因为Blog的没落 微博的兴起 xff0c 确实给了很多人表达情绪的方式 如果我能够用140个字 xff0c 清晰的表达我的观点 xff0c 并且能够让我的读者做到一点点认可 x
  • Android Gradle Plugins系列-01-自定义Gradle插件入门指南

    前言 本文内容已经有很多大佬写过了 xff0c 不过这里为了知识体系的完整 xff0c 就再写一遍 xff0c 并加入Maven Publish插件的使用 xff0c 不感兴趣跳过就好 官方文档 xff1a Developing Custo
  • Android Gradle Plugins系列-02-Maven Publish 插件踩坑指南

    前言 可能有读者会疑惑 xff0c Maven Publish 插件又是啥玩意 xff1f 确定不是Maven插件吗 xff1f 不要逗我 让笔者慢慢道来 xff0c 如果你刚好把AndroidStudio升级到Android Studio
  • Android Jetpack系列-实现Application作用域的共享ViewModel用于Activity和Fragment的相互通信

    目录 前言 搞清楚activityViewModels的本质 定义Application作用域的ViewMode ApplicationViewModelLazy BaseViewModelApplication 使用方法 自定义Appli
  • Android 音视频开发实践系列-04-Android WebRTC推流到SRS服务器实现直播功能

    目录 前言 了解WebRTC 部署SRS服务器 下载源码并运行 可能遇到的问题 create session create session add publisher publish negotiate no found valid H 2
  • 搭建Android日志系统 美团点评大前端Logan入门指南

    目录 前言 总览 快速开始 下载官方源码 使用IDEA打开Logan Server项目 使用Docker下载MySQL镜像并启动容器 运行Logan Server 修改db properties 修改log4j properties 配置M