灰度发布集群服务进行生产环境的日志打印调试
情景模拟:当项目发布之后,某些功能在经过某次确定的操作【1】之后,使另一操作【2】不能正常使用。而当时的源代码由于技术升级,恢复比较困难。在此情景下,需要在线调试。当没有执行操作【1】时,打印操作【2】在执行过程中关键对象的属性值日志信息;当执行了操作【1】时,打印操作【2】在执行过程中关键对象的属性值日志信息。
系统环境: Centos7、Docker1.12.1
应用环境:分布式集群服务,Docker容器化发布,多节点服务应用
说明:
1.在支持打印日志的过程中需要替换Docker容器中的jar包,要求Docker支持docker cp命令;
2.替换的jar和被替换的jar包生成的jdk版本需要一致;
3.在jar包替换之前,最好在容器里先进行备份,然后从容器外拷贝有日志生成功能的相同命名的jar包予以覆盖;
4.由于jar替换之后,容器需要重启,替换的结果才能生效,所以该项目最好是分布式集群服务,支持灰度发布。
- 问题再现与定位:
由于jdk提供的加密 【Cipher 是有状态的,而且是线程不安全的】,即在同一应用中,使用不同的加密方式,彼此之间可能会有影响。在项目上线后,加密模块不定期的会出现加密后的加密串,协作的系统解不出原密码的问题。在此问题下,Cipher对象的变量就成为一个关键的因素,只要在出现问题时,和正常运行时打印出其内的属性值,就可以定位到具体问题,从而进一步解决问题。
在此猜想之下,由于加密模块是自己实现的,那么就可以在获取ciper对象之后打印出其内的属性值,而由于其内的属性几乎都没有提供get方法,所以考虑采用反射机制获取其属性值信息。具体编码如下:
//打印日志 private static Logger loggerError = LoggerFactory.getLogger("com.changan.sso.sdk"); /** * 通过反射机制打印出对象的属性值信息 * * @param object * @throws IllegalArgumentException * @throws IllegalAccessException */ private static void getObjectMessage(Object object) throws IllegalArgumentException, IllegalAccessException { Class userCla = (Class) object.getClass(); /* * 得到类中的所有属性集合 */ Field[] fs = userCla.getDeclaredFields(); for (int i = 0; i < fs.length; i++) { Field f = fs[i]; f.setAccessible(true); // 设置些属性是可以访问的 Object val = f.get(object); // 得到此属性的值 loggerError.info("name:" + f.getName() + "/t value = " + val); } } /** * 加密 * * @param plaintext * @param key * @return */ public static String encrypt(String plaintext, Key key) { try { // 使用密钥加密 Cipher cipher = Cipher.getInstance("RSA"); loggerError.info("*********************加密cipher信息打印开始******************"); getObjectMessage(cipher); loggerError.info("*********************加密cipher信息打印结束******************"); cipher.init(Cipher.ENCRYPT_MODE, key); // 加密后 byte[] result = cipher.doFinal(plaintext.getBytes(UTF8)); // 返回结果 return KeyUtil.encryptBASE64(result); } catch (Exception e) { // 打印日志 loggerError.error("加密异常", e); return null; } }
将此代码编写好之后,采用Docker容器中的jdk版本进行编译,必要时构建成jar,并上传至服务器上,通过以下命令将其拷贝到Docker容器内相对应的目录下:
$ docker cp test-1.0.8-SNAPSHOT.jar c33b415ec2de:/apache-tomcat/webapps/test/WEB-INF/lib/
在logback.xml中增加打印给日志信息的文件支持:
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径 --> <property name="LOG_HOME" value="/data/core/logs" /> <!-- sso.sdk相关 --> <appender name="sso.sdk" class="ch.qos.logback.core.rolling.RollingFileAppender"> <Encoding>UTF-8</Encoding> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <!-- 按天回滚 daily --> <fileNamePattern>${LOG_HOME}/com.changan.sso.sdk-%d{yyyy-MM-dd}.log </fileNamePattern> <!-- 日志最大的历史 30天 --> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n </pattern> </encoder> </appender> <!-- root --> <logger level="INFO" name="com.changan.sso.sdk"> <appender-ref ref="sso.sdk" /> </logger> <!-- root --> <root level="INFO"> <appender-ref ref="console" /> <appender-ref ref="defaultLogFile" /> </root>
添加完毕后,保存并退出容器。执行容器重启命令:
$ docker restart test-web
随后安装执行正确的加密方式调用一次,在按照执行错误的加密方式调用一次,获取的日志信息如下:
从中明显可以得出,两次加密的加密提供商明显不同,问题定位准确,达到检验推测的目的。