외국 사이트를 찾아보니, How to 라는 항목으로 존재했다.

http://www.howtoforge.com/how-to-install-vmware-tools-on-pfsense-freebsd

문제는 32bit 버전에 pfSense 과거 버전 기준으로 작성된 것 같았다.

그래서 이번에 설치했던, pfSense 2.1 x64용 안정화 버전을 가준으로 수정한 내용을 정리한다.
pfSense 2.1은 FreeBSD 8.3 기반으로 구성되어 있어 이에 맞게 스크립트 실행을 변경한다.

 

설치방법

제일 먼저 콘솔을 띄운다. SSH도 좋지만, 이왕 방화벽 22번 포트를 연결하기 보다, 차라리 Console을 직접 연다.
image

그리고 난 뒤, 콘솔로 들어가 다음 명령들을 입력한다.

setenv PACKAGEROOT "ftp://ftp.freebsd.org"

원본 사이트에서 일부 다른 부분이 바로 이 부분이다.

setenv PACKAGESITE "ftp://ftp.freebsd.org/pub/FreeBSD/ports/amd64/packages-8.3-release/Latest/"

이제부터 VMTools를 설치하기 위한 관련 패키지들을 설치한다.

pkg_add -v -r perl
pkg_add -v -r compat6x-amd64
자 이제 설치를 하기 위한 이미지를 삽입한다.

image

Temp 폴더를 하나 만들어서 VMTools 시디 이미지를 마운트 한다.
cd 
mkdir tmp2
mkdir tmpp
mount_cd9660 /dev/acd0 /tmp2
cd /tmp2

마운트 되었으면 vmtools 원본을 복사하고 압축을 푼다.

cp vmware-freebsd-tools.tar.gz /tmpp
cd /tmpp
tar -zxvf vmware-freebsd-tools.tar.gz
cd vmware-tools-distrib/

이제 compat6x 라이브러리들을 준비해야 한다. VMWare에서 사용되는 라이브러리들인데, 기본적으로는 처리되지 않는 것들이기 때문에, 수작업으로 연결한다.

ln -s /usr/local/lib/compat/libm.so.4 /lib
ln -s /usr/local/lib/compat/libc.so.6 /lib
ln -s /usr/local/lib/compat/libthr.so.2 /lib

실행하기 위해서는 설치 프로그램 스크립트를 실행형태로 만듭니다. 그리고 실행합니다.

chmod +x vmware-install.pl bin/vmware-config-tools.pl bin/vmware-uninstall-tools.pl
./vmware-install.pl

실행하면, 무언가 메시지들이 뜨면서 입력을 대기합니다. 특별히 변경할 내용은 없으므로, 계속 Enter를 치고 넘어가면 자동적으로 설치가 되면서, 맨 나중에 Enjoy 어쩌고 저쩌고가 뜨면 완료된 겁니다.

이제 자동 실행이 될 수 있도록 수정합니다

echo '#\!/bin/sh' > /usr/local/etc/rc.d/000-ldconfig.sh 
echo '/sbin/ldconfig -m /usr/local/lib/compat' >> /usr/local/etc/rc.d/000-ldconfig.sh
chmod a+x /usr/local/etc/rc.d/000-ldconfig.sh 

이제 작업했던 파일들을 삭제하고 reboot를 한다.

cd /
rm -r /tmpp/
rmdir tmpp
shutdown -r now

 

정리

분명 VMTools에서 제공된 드라이버들은 업데이트 된 것 같다.
다만, 현재 자동으로 VMTools가 안떠서 현재는 더 고민 중이다. ( HOST에서 감시 도구가 떠줘야 관리를 하는데, 현재 뜨지 않아서 관리모드가 안되고 있다. ) 몇가지 더 테스트를 해보고, 되는대로 업데이트 할 예정이다.

728x90

자료 출처

2CPU의 가상화 게시판 : http://2cpu.co.kr/bbs/board.php?bo_table=vm
XPENology Forum : http://xpenology.com/forum/index.php
DomStation 블로그 : http://www.domstation.com/opware-bootstrap-and-open-vm-tools-installation/
Idiot's Guide to DSM 4.2 and ESXi 5.1.docx : http://depositfiles.com/files/virzefc1a

2CPU 장터를 통해 XW4600을 구하게되었고, 무사히 Esxi 5.5를 설치했다.
( 벤더 제품 중 가장 Native 하다고 생각되는 HP. 역시 큰 문제 없이 한번에 Esxi 5.5를 설치할 수 있었다.)

테스트를 위한 Windows Server를 설치하고 나니, 왠지 내부적으로 사용할만한 NAS가 필요했고,
이ㅔ XPEnology ( Synology OS를 커스텀화한 NAS 운영체제)를 설치하게 되었다.
편리한 UI 와 Extention이 가능한 추가 기능들은 매우 매력적일 수 밖에 없었기에, 자연스럽게 설치를 시작하게 되었다.

생각보다 진입점이 높아서(구축사례가 많지만, 기록물이 적은듯...) 검색어에 잘 걸리지 않아, 결국 2CPU를 통해 하나씩 접근해보았고, 그에 맞춰 성공한 사례를 기반으로 새로 하나 더 만들면서 기록해 본다.

 

1. 준비하기.

먼저 XPENology 파일들은 모두 7zip으로 압축되어 있다.
7zip 부터 설치해놓자. ( http://www.7-zip.org )

다음은, Esxi용 이미지가 필요하다.
처음에는 설치용 미디어만 있으면 될 줄 알았으나, 그 방법은 직접 PC 혹은 서버에 설치할 때이였고, 실제로는 Esxi용으로 별도로 만든 이미지가 필요했다. 이 파일은 다음 경로에서 다운이 가능하다.
http://xpenology.trantor.be/esxi/

image

image

image

다운을 받으면 7zip으로 압축된 파일을 다운 받는다. 이 파일을 받아 압축을 풀면 VMDK 파일이 생기는데, 이 파일이 가상머신에서 사용될 HDD 디스크 파일이다. 일단 이 파일을 저장해 놓는다.
( 현재 여기서 사용한 파일의 파일이름은 synoboot_trantor_3810_esxi_v1.1.vmdk 이다. )

그리고, 실제 XPENology를 구동하기 위한 프로그램들을 담은 Patch 파일이 필요하다.
이 파일은 배포본을 다운 받으면 된다.

image

image

image

파일을 다운로드 해서 압축을 풀면, 두개의 파일이 나오는데, 그 중 PAT 파일이 필요하다. 이 파일도 보관하자.
( 현재 여기서 사용한 파일의 파일이름은 XPEnology_trantor_v1.0_DSM_DS3612xs_3810.pat 이다. )

마지막으로 Synology Asist 라는 프로그램을 다운 받는다.
XPENology를 설치했을때, 어디에 설치되어 있는지 부터, 최초 구성작업을 이도구를 통해서 작업할 수 있어 매우 편하다. 경로는 http://global.download.synology.com/download/Tools/SynologyAssistant/4359/Windows/SynologyAssistantSetup-4.3-4359.exe 이다.

설치용 프로그램이므로 다운받은 뒤 실행해서,  반드시 설치해 놓도록 한다.

 

 

2. VM 만들기.

이제 ESXI 서버에 접속하자. 콘솔을 통해서 업로드하고, CLI 입력들을 해서 처리할 수 있겠지만, 편한 GUI를 사용하여 작업할 수 있기 때문에, 여기서는 GUI 기반으로 설명한다. ( 사실 필자도 CLI는 잘 사용하지 못한다. )

vSphere Client를 통해 Esxi 를 접속한다.

image

서버가 표시되었을 때, 서버에서 Context Menu를 띄워 "New Virtual Manchine"으로 들어간다.

image

Configuration Type은 Custom으로 한다

image

적당한 이름을 정한다.

image

VM 파일들이 저장될 위치를 선정한다. 나중에 VM Image를 올릴 위치이므로 어디다 VM을 만들었는지 기억해 놓도록 한다.

image

가상 머신의 버전을 선택하는 화면이 나온다.
여기서는 호환성 문제가 걸리지만 않는다면, 최대한 최신 설정으로 진행한다.

image

설치할 게스트의 운영체제를 선택하는 화면이 나온다.
여기서는 Linux로 하고, 2.6.X 버전의 리눅스를 사용한다고 체크한다.

image

CPU 설정은 취향대로 하면 된다.
다만, 다양한 기능들을 사용할 예정이면, Core 갯수를 늘리는 것도 좋다.
여기서는 1 이지만, 앞서 설정했던 XPEnology에서는 Core를 2개로 잡았다.

image

메모리 설정하는 부분이다.
이 역시 CPU 처럼 고성능의 작업이 필요하면 용량을 늘리도록 한다.
다만, 굳이 1G 이상을 안잡아도 생각보다 성능이 훌륭하게 나오는 편이다.
image

네트워크 설정 화면이 나온다.
각자 ESXI가 구성된 형태에 따라 알아서 잡도록 한다.

image

SCSI 컨트롤러 선택화면이 나온다.
Synology가 연결되는 형태의 SCSI 컨트롤러로 바로 잡히려면, 맨 아래 쪽에 있는 VMWare Paravirtual을 선택하도록 한다. 다른 옵션들로 연결이 잘 안되는 것 같다. ( XPENology에 해당 컨트롤러 드라이버가 없는 듯 싶다. )

image

저장소로 사용할 디스크를 생성한다.
실제적으로 XPENology 프로그램 및 저장장소로 사용될 공간을 잡는다.
여기서는 적당히 120G로 설정했다. 이 설정은 VM 설정으로도 추가적으로 잡을 수 있으므로, 적당히 잡도록 한다.

image

image

image

이제 Summary가 나왔으면 Finish를 누르도록 한다.

image

 

3. VM 구성

자 기본틀은 완성되었다. 이제 기본으로 제공한 VM을 이용해 XPEnonlogy를 구축하기 위한 작업을 한다.

먼저 앞서 다운 받은 vmdk 파일을 저 VM이 위치한 폴더에 업로드한다.
이를 위해서는 Brower DataStore를 띄워야 한다. 이 위치 정보는 앞서 VM을 생성할 때의 위치에 해당하는 저장소를 열도록 한다.

image

Datastore Browser를 띄웠으면 해당 폴더를 열도록 한다.

image

이제 아까의 VMDK 파일을 업로드 한다.

image

업로드가 완료되었으면 이제는 해당 VM의 Edit Setings 메뉴를 띄워 Properties 창을 띄운다.

image

VM 의 Properties 창이 떴으면 Hardware 탭에 있는 Add 버튼을 클릭하고, 새로 뜬 창에서
Hard Drive를 선택하도록 한다.

image

이제 업로드한 HDD를 연결한다. "Use an exsisting virtual disk"를 선택한다.

image

자 이제 업로드한 파일을 선택하고 Open을 한다.

image

IDE 0:0 로 되어있는지 확인하고 넘어간다.

image

완료 처리한다.

image

 

4.XPENology 설치

이제 XPENonlogy를 위한 VM 작업은 다 끝났다. 이제 VM을 실행하도록 한다.
실행하면서 Console 창을 띄워보면 뭐라 뭐라 Linux 부팅이 되고 최종적으로 "Diskstation login:" 이라는 문구로 끝날 것이다.

image

만일 띄우지 못했다면, Booting 순서 문제일 수 있으니, 재부팅을 한 뒤, 바이오스에 진입(F2 로 들어감)하여, 부팅 순서가 SCSI 보다 IDE HDD가 먼저 나올 수 있도록 한다. ( 부팅이 한순간이므로 부팅되자 마자 매우 빠른 F2 연타가 필요할 수 있다. )

image

이제 XPESynology를 구성하도록 한다. 이를 위해서 앞서 설치한 Synology Assist를 사용한다.
Synology Assist 프로그램을 실행한다.

그러면 자동적으로 설치된 XPENology를 볼 수 있다.
(만일 볼 수 없다면, 같은 네트워크 대역에 있는지 확인하고, 해당 XPENology VM이 제대로 떴는지 등을 확인하도록 한다. 예를 들면 Synology Assist가 실행되는 PC의 IP는 192.168.0.101 인데, XPENology의 IP는 192.168.102.8 인 경우 192.168.0.X 와 192.168.102.X는 서로 대역이 다르므로 직접 연결하여 찾지 못한다. 단순하게 말하자면, 같은 공유기 내에 있어야 된다는 의미.)

image

자, 연결된 개체서 Context Menu를 띄워 설치를 클릭한다.

image

이제 설정 마법사가 뜬다.
설치 파일 경로를 입력해달라는 위치에, 앞서 다운 받은 PAT 파일을 연결한다.

image

PAT를 정상적으로 연결했으면, 이제는 서버 정보를 입력한다.
여기서는 admin의 암호를 설정하고, 서버 이름등을 설정하면 된다.
admin 암호만 입력해도 된다. ( 서버이름의 기본값이 DiskStation 이다. )

image

SHR 볼륨 생성에 대한 체크가 된 상태로 하면 경고 창이 뜨는데, "확인" 누르고 진행하도록 한다.

이제 네트워크 설정을 한다. 가급적이면 IP를 고정해서 설정하도록 한다.
그래야 접근할 때 쉽기 때문이다.

image

그러면 설치 진행 화면이 나오는데, 큰 문제가 없으면 체크가 다 표시되면서 종료 버튼이 나오는데,
그 버튼을 클릭하면 된다.

image

 

5. VM Tools 설치.

사실 여기까지만 오면 완료되긴 했지만, 문제는 Esxi 서버에서 이 XPENology 서버를 끄질 못한다. 즉 직접 XPEnology를 콘솔로 접근하던가 해서 강제로 꺼야 된다는 말. 전원 자체를 내리는 Power Off는 되지만, Shutdown이 안된다는 의미이다.

그래서 ESXI에서 Shutdown Guest 제어가 되려면 VM Tools를 설치해야 한다. 문제는 ESXI에서 제공하는 VM Tools를 설치하기가 쉽지 않은데다가, 이 XPENology와 호환이 안된다. 그래서 다른 방식으로 진행해야 한다.

먼저 Putty 같은 telent 접속 프로그램이 필요하다. ( http://www.chiark.greenend.org.uk/~sgtatham/putty/ 설치용 프로그램이 아니므로 실행 파일만 받아서 실행하면 된다. )

실행하면 주소 창이 뜨는데, 그 안에 XPEnology의 주소를 입력한다. ( 앞서 XPENology 설치할 때의 IP 주소)

image

인증서 부분의 Warning이 뜨는데, Yes를 눌러 진행한다.

image

그러면 콘솔 접속 화면이 뜨는데, ID는 root 로, 암호는 앞서 만든 admin의 암호를 넣는다.

image

이제 명령 창과 함께 명령어들을 잘 입력하면 된다.

  1. 먼저 temp 디렉토리로 이동한다.
    cd /volume1/@tmp
  2. bootstrap 을 다운로드 받는다.
    wget  http://ipkg.nslu2-linux.org/feeds/optware/syno-i686/cross/unstable/syno-i686-bootstrap_1.2-7_i686.xsh
  3. 다운 받은 bootstrap 파일을 실행 파일로 만든다.
    chmod +x syno-i686-bootstrap_1.2-7_i686.xsh
  4. .xsh 파일을 실행한다.
    sh syno-i686-bootstrap_1.2-7_i686.xsh
  5. 이제 실행했던 스크립트 파일을 삭제한다.
    rm syno-i686-bootstrap_1.2-7_i686.xsh
  6. PATH의 경로를 변경하는 작업을 한다. 이를 위하 vi를 띄운다.
    vi /root/.profile
  7. vi에서 다음과 같이 입력하여 PATH의 문자열을 바꾼다.
    :%s/PATH=/PATH=$PATH:/
  8. vi에서 다음과 같이 입력하여 저장하고 닫는다
    :wq!
  9. 재부팅한다.
    reboot
  10. 재부팅이 완료되었으면, 다시 putty를 실행해서 다시 로그인해서 연결한다.
  11. 이제 ipkg를 이용해 내부 패키지를 정리한다.
    ipkg update
    ipkg upgrade
  12. 다시 temp 디렉토리로 이동한다.
    cd /volume1/@tmp
  13. open vm tools를 다운 받는다.
    wget http://users.skynet.be/synology/i686/syno_vmware_kernel_mod_x86_64_3.2.30.zip
  14. 다운 받은 파일의 압축을 해제한다.
    unzip syno_vmware_kernel_mod_x86_64_3.2.30.zip
  15. 압축이 풀린 경로로 들어간다.
    cd syno_vmware_kernel_mod_x86_64_3.2.30
  16. sh용 파일을 실행 가능하게 만든다.
    chmod +x S37vmware.sh
  17. sh용 실행한다.
    sh S37vmware.sh start
  18. 이제 Tools의 실제 동작하는 파일들을 설치한다.
    ipkg install http://users.skynet.be/synology/i686/open-vm-tools_9.2.3-1031360-1_i686.ipk
  19. vmtools가 자동으로 실행될 수 있도록 구성 준비한다.
    cd /opt/etc/init.d/
  20. 자동 스크립트를 다운 받는다.
    wget http://www.domstation.com/wp-content/uploads/2014/01/S22open-vm-tools-v1.1.zip
  21. 스크립트의 압축을 푼다.
    unzip S22open-vm-tools-v1.1.zip
  22. 스크립트를 실행 가능하도록 만들어 준다.
    chmod +x S22open-vm-tools.sh
  23. 다운 받은 zip 파일은 삭제하고 reboot를 한다.
    rm S22open-vm-tools-v1.1.zip
    reboot

명령 줄을 입력할 내용을 쭉 나열해서 그렇지, 사실 별 내용은 없다. 이제 root 되고 난 뒤, 완전히 부팅되었으면, Esxi 관리도구에서 해당 VM을 종료해본다. 이제는 제대로 종료가 되는 것을 확인할 수 있다.

 

정리.

사실 모든 정보는 준비되어 있었고, 자료도 있었지만, 여기저기에 단편적으로 나뉘어 있는데다가, 대부분 영어권 자료다 보니 쉬이 적용을 못하고 있었다. 하지만, 막상 해보니 큰 어려운은 없었던것 같다.

나중에 2~3T 짜리 HDD가 들어오면 용량을 넉넉히 붙여서 내 개인 저장 장소로 활용해보도록 해야 겠다.

728x90

Visual Studio 안에 있는 Setup Project로 간단한 설치 프로그램을 만들곤 한다.
(복잡한 설치 구성이 필요 없는 경우 이 설치기능은 참 편하다)

그런데, 프로그램을 일부 수정해서 다시 말아서 배포하는 경우가 있다. 
이때  새로 만든 설치 본으로 설치하려면 다음과 같은 메시지가 떠서 상당히 곤혹스럽게 만들 때가 있다.
img20140115121930001

즉, 이미 다른 버전이 설치되어 있어서 설치가 불가능하므로, 이전에 설치된 버전을 삭제한 후, 다시 설치하라는 의미이다. 업그레이드와는 다른, 즉 이전 버전인지 신규 버전인지를 판단하지 못하는 경우를 의미한다.

그럼 이 문제는 어떻게 해결 해야 하는 걸까?
고민 중에 다음과 같은 문서가 있는 링크를 찾았다.

What are Upgrade, Product and Package Codes used for?

MSI를 구성할 때, 제품만의 고유한 버전을 위해 3가지 값을 사용한다.

Update Code, Product Code, Package Code.

이 세가지 값을 이용하여, 설치된 프로그램의 종류, 버전 등을 구분하여 제공하게 된다. 즉 프로그램 설치/삭제/수정 등의 모든 작업을 운영체제에서 처리할 때, 사용자들이 알아보는 문자열 대신 일종의 일련 번호로써 역할을 하게 되는 것이다. 여기서는 해당 하는 값들과, 그 역할들을 위의 링크에 있는 페이지 내용을 기반을 그대로 옮겨 적어 기록한다.

각 코드 값은 GUID라는 고유 값을 문자열 형태로 나타내도록 되어 있다.
( 예를 들면 {FB23FA93-3A96-4C91-B441-042F22C88BA4} 과 같은 값 )

 

MSI에서의 UpgradeCode란?

Upgrade Code란, 만든 Application을 대표하는 값을 의미한다. 같은 프로그램은 아니지만, 하나의 계열을 이루고 있을 때, 이 코드가 사용된다. 예를 들면 MS Office 제품군 과 같은 계열로 구성되었을 때 같은 제품이라는 의미를 갖게 해주는 것이다. Office 2010 을 예로 들었을 때, Office 2010을 설치하면 Word 2010, Excel 2010 이런 제품들이 설치되게 된다. 이것을 하나로 아우르는 표현을 할 때, Word 2010도, Excel 2010도 같은 Upgrade Code를 갖게된다.

만일 Upgrade Code가 달라지는 경우, 아예 프로그램이 다르다! 라고 인식하게 된다. 그러므로, 완전히 틀려지는 제품이 아닌 경우에는 이 코드의 값을 변경하지 말아야 한다.

MSI에서의 Product Code란?

Product Code란, 같은 프로그램인데, 언어나, 버전이 틀린 경우 그 구분을 위해 제공되는 값이다. 즉 프로그램이 업그레이드를 했거나, 언어가 다른 경우 이 코드 값을 달리해서 적용해야 한다. 이 코드 값을 이용해 실질적인 업그레이드나 추가 작업을 수행하게 된다.

MSI에서의 Pakage Code 란?

앞서 설명한 Upgrade Code 및 Product Code와는 다르게, MSI 파일 자체에 대한 구분을 위한 버전이다. 작은 설치용 프로그램인 경우에는 크게 문제가 없지만, 만일 여러가지 구성요소들을 구분지어 설치파일을 만든 경우 MSI 구성요소들이 달라지게 된다. 즉 각각의 설치 구성요소 별로 구분을 짓기 위해서 이 Package Code를 구성하게 된다.
이 부분은 프로그램의 업그레이드 보다, 패치와 같은 단위 구성요소의 업데이트를 할 때 이 값을 이용해서 설치파일을 구성하게 된다.

 

각 코드 값을 이용한 프로그램 추가/삭제 동작 정리

사실 단순하게 코드 값을 변경해서 이런 저런 설치 동작을 하면 쉽게 이해가 되긴 하지만, 의외로 Setup 프로그램을 만들어 매번 등록 삭제 하려면 정리가 쉽진 않다. 그래서 각 코드의 변경에 따라 어떻게 동작하는지를 정리했다.
(이 내용은 앞서 언급한 링크의 내용을 참고로 구성했다

  1. 같은 Product Code에 같은 Package Code로 된 MSI를 설치하려는 경우 “수정” 혹은 “삭제” 동작을 수행하게 된다.
  2. 같은 Product Code에 다른 Package Code로 된 MSI를 설치하려는 경우 실행되지 않는다. 이 경우 맨 처음 필자가 겪은 메시지창이 뜨면서 설치가 되지 않는다.
  3. 다른 Product Code에 같은 Package Code로 설치하는 경우 “수정” 혹은 “삭제” 동작을 수행하게 된다. 만일 RemovePreviosVersion이 True인 경우 기존 버전을 삭제하고 재설치를 수행하게 된다.
  4. 다른 Product Code에, 다른 Package Code로 설치하는 경우, 인스톨러는 완전히 다른 제품으로 인식해서 새로 설치하게 된다. (중복 설치)

즉 필자 처럼 단순 업그레이드로 구성하려면, Setup Project 옵션의 RemovePreviosVersions를 True로 한 뒤, 새로운 Product Code를 구성해야 한다.

일단, 자동 빌드 구성에 위의 내용을 기반으로 해보니, 별다른 오류 없이 제대로 설치가 되었다.
나중에 Patch 판을 만드는 방법도 고민해봐야 겠다.

728x90

앞의 포스트에서 log4net의 동적 설정에 대해서 이런저런 이야기를 펼쳤다.

이 기준을 가지고 몇 가지 수정하면서 간단한 설정용 클래스를 만들었다.
물론 Rolling File에 대한 설정만 되는 간단한 구조이기 때문에 추후 지속적인 업데이트가 필요하다.
다만, 프로젝트들을 하면서 매번 코드 덩어리를 붙여넣기에 한계가 있어, 아예 클래스로 구성해봤다.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace net.knoie
{
    public class LoggerSettings
    {
        private string m_sLogFullFilename = string.Empty;

        public  static LoggerSettings Current
        {
            get;
            private set;
        }

        public static void CreateInstance(string sLogFullFilename)
        {
            Current = new LoggerSettings(sLogFullFilename);
        }

        private LoggerSettings(string sLogFullFilename)
        {
            m_sLogFullFilename = sLogFullFilename;
            createLoggerSettings();
        }

        public int LogLevel
        {
            get
            {
                if (CurLogger.Level == log4net.Core.Level.Alert)
                    return 1;
                if (CurLogger.Level == log4net.Core.Level.All)
                    return 2;
                if (CurLogger.Level == log4net.Core.Level.Critical)
                    return 3;
                if (CurLogger.Level == log4net.Core.Level.Debug)
                    return 4;
                if (CurLogger.Level == log4net.Core.Level.Emergency)
                    return 5;
                if (CurLogger.Level == log4net.Core.Level.Error)
                    return 6;
                if (CurLogger.Level == log4net.Core.Level.Fatal    )
                    return 7;
                if (CurLogger.Level == log4net.Core.Level.Fine    )
                    return 8;
                if (CurLogger.Level == log4net.Core.Level.Finer    )
                    return 9;
                if (CurLogger.Level == log4net.Core.Level.Finest    )
                    return 10;
                if (CurLogger.Level == log4net.Core.Level.Info    )
                    return 11;
                if (CurLogger.Level == log4net.Core.Level.Log4Net_Debug        )
                    return 12;
                if (CurLogger.Level == log4net.Core.Level.Notice        )
                    return 13;
                if (CurLogger.Level == log4net.Core.Level.Off        )
                    return 14;
                if (CurLogger.Level == log4net.Core.Level.Severe        )
                    return 15;
                if (CurLogger.Level == log4net.Core.Level.Trace            )
                    return 16;
                 if (CurLogger.Level == log4net.Core.Level.Verbose)
                    return 17;
                if (CurLogger.Level == log4net.Core.Level.Warn)
                    return 18;
                return 14;
            }
            set
            {
                switch (value)
                {
                    case 0:
                        this.Level = log4net.Core.Level.Off;
                        break;
                    case 1:
                        this.Level = log4net.Core.Level.Alert;
                        break;
                    case 2:
                        this.Level = log4net.Core.Level.All;
                        break;
                    case 3:
                        this.Level = log4net.Core.Level.Critical;
                        break;
                    case 4:
                        this.Level = log4net.Core.Level.Debug;
                        break;
                    case 5:
                        this.Level = log4net.Core.Level.Emergency;
                        break;
                    case 6:
                        this.Level = log4net.Core.Level.Error;
                        break;
                    case 7:
                        this.Level = log4net.Core.Level.Fatal;
                        break;
                    case 8:
                        this.Level = log4net.Core.Level.Fine;
                        break;
                    case 9:
                        this.Level = log4net.Core.Level.Finer;
                        break;
                    case 10:
                        this.Level = log4net.Core.Level.Finest;
                        break;
                    case 11:
                        this.Level = log4net.Core.Level.Info;
                        break;
                    case 12:
                        this.Level = log4net.Core.Level.Log4Net_Debug;
                        break;
                    case 13:
                        this.Level = log4net.Core.Level.Notice;
                        break;
                    case 14:
                        this.Level = log4net.Core.Level.Off;                        
                        break;
                    case 15:
                        this.Level = log4net.Core.Level.Severe;
                        break;
                    case 16:
                        this.Level = log4net.Core.Level.Trace;
                        break;
                    case 17:
                        this.Level = log4net.Core.Level.Verbose;
                        break;
                    case 18:
                        this.Level = log4net.Core.Level.Warn;
                        break;
                }
            }
        }

        public log4net.Core.Level Level
        {
            get
            {
                return CurLogger.Level;
            }
            set
            {
                CurLogger.Level = value;
            }
        }

        public bool EnableRollingAppender
        {
            get
            {
                foreach (log4net.Appender.AppenderSkeleton appender in CurLogger.Appenders)
                {
                    if (appender is log4net.Appender.RollingFileAppender)
                        return true;
                }
                return false;
            }
            set
            {
                if (value)
                {
                    if (this.EnableRollingAppender == false)
                    {
                        string sLogPath = m_sLogFullFilename;
                        log4net.Appender.RollingFileAppender rollingAppender = CreateRollingFileAppender(sLogPath);
                        CurLogger.AddAppender(rollingAppender);
                        rollingAppender.ActivateOptions();
                    }
                }
                else
                {
                    if (this.EnableRollingAppender)
                    {
                        log4net.Appender.RollingFileAppender rollingAppender = null;
                        foreach (log4net.Appender.IAppender appender in CurLogger.Appenders)
                        {
                            if (appender is log4net.Appender.RollingFileAppender)
                            {
                                rollingAppender = (log4net.Appender.RollingFileAppender)appender;
                                break;
                            }
                        }
                        if (rollingAppender != null)
                            CurLogger.RemoveAppender(rollingAppender);
                    }
                }
            }
        }

        private log4net.Repository.Hierarchy.Logger CurLogger
        {
            get
            {
                log4net.Repository.Hierarchy.Hierarchy hierarchy = (log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetRepository();
                return hierarchy.Root;
            }
        }

        private void createLoggerSettings()
        {
            try
            {
                log4net.Repository.Hierarchy.Hierarchy hierarchy = (log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetRepository();
                hierarchy.Configured = true;
            }
            catch 
            {
            }
        }

        private log4net.Appender.RollingFileAppender CreateRollingFileAppender(string sLogPath)
        {
            log4net.Appender.RollingFileAppender rollingAppender = new log4net.Appender.RollingFileAppender();
            rollingAppender.File = sLogPath;
            rollingAppender.AppendToFile = true;
            rollingAppender.RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Date;
            rollingAppender.LockingModel = new log4net.Appender.FileAppender.MinimalLock();
            rollingAppender.DatePattern = "_yyyyMMdd\".log\"";
            log4net.Layout.PatternLayout layout = new log4net.Layout.PatternLayout("%date %-5level %logger - %message%newline");
            rollingAppender.Layout = layout;
            return rollingAppender;
        }
    }
}

이 코드를 사용하려면, log를 사용하기 전에 실행되는 위치에 다음과 같은 코드를 넣어주어야 한다.

string sLogPath = "C:\Temp\this.log";
net.knoie.LoggerSettings.CreateInstance(sLogPath);
net.knoie.LoggerSettings.Current.EnableRollingAppender = true;
net.knoie.LoggerSettings.Current.Level = log4net.Core.Level.All;

sLogPath 라는 부분이 있는데, 이 안에 로그파일이 생성되어야 할 위치를 설정해 주면 된다.

이 클래스와 테스트 프로그램은 첨부파일로 같이 첨부한다.
( 첨부된 예제는 Visual Studio 2010 으로 작성된 WinForm Application 이다.)


MyTestProject.zip


728x90

log4j 라는 Apache 프로젝트가 있다. ( http://logging.apache.org/log4j )
java 내에 동작하는 각종 Log 내용을 쌓기 위한 모듈인데, 내부적으로 어떠한 설계로 구성되었는지는 살펴보지 않아, 잘은 모르지만, 성능적 저하 없이 많은 양의 Log 관리에 훌륭하게 대처 할 수 있다. 또, Log로 남겨지는 형태를 단순 Text 파일 형식에서 부터 DB 파일까지 그 대응적 능력이 우수하다.

.NET 프로그램에서도 이 강력한 log4j를 사용할 수 있는데, 그 도구가 바로 log4net 이다. ( http://logging.apache.org/log4net/ )

여기에 쓸 내용은 지금까지 Application 내에 적용하면서 진행되었던 사항들을 적용 순서 대로 나열할 예정이다. 그래서 그 순서가 log4net에 대한 기초와는 다르다. 그러다 보니, 독자가 현재 적용 중인 Project나 구성과는 사뭇 다를 수 있으며, 스스로 잘못 이해한 부분으로 인한 오류도 있다.
더욱이 Web-Base가 아닌 WinForm-Base로 제작하면서 익힌 기능이다 보니, Web 기반의 구성과 일부 차이가 발생할 수 도 있다. 이점을 이해하면서 살펴보면 좋을 것 같다. 또, 이 글 외에도 많은 log4net에 대한 예제, 구성 이야기들을 살펴볼 수 있으므로, 사뭇 달라도, 자신의 적용과 유사한 다른 글들을 찾아보기도 쉬울 것이다.

 

log4net 접근 시작

최초에는 독자적으로 log 파일을 쌓고 구성했다. 그런데, 문제는 Single Thread 기반의 Application이다 보니, 로그 쌓는데, 의외로 많은 Process Time을 잡아 먹는 문제점이 발생했다. 로그는 좀 더 Detail 하게 쌓고 싶었지만, 그 성능적 문제로 인해 log를 쌓기에 두려움까지 얻게 되었다.

그러다가, java 관련 프로젝트를 수행하면서 log4j 에 대한 접근을 좀 하고 나니, log 쌓는 작업에 대한 대치성에 대해서 고민하게 되었고, 잠시 짬을 두어 log4net을 살펴보았다.

오픈 소스다 보니, 소스 자체를 공개하고 있으며, 다양한 Platform에 대한 컴파일이 이미 되어 있어, 사실 자신이 원하는 프로젝트에 대한 Platform에 맞추어 미리 컴파일된 버전을 바로 연결해서 사용가능 하다.
http://logging.apache.org/log4net/download_log4net.cgi 에서 다운로드 페이지 내에, Binaries 중,
log4net-1.2.13-bin-newkey.zip 을 다운 받았다. 그 안을 열면 여러가지 폴더가 있는데, bin 폴더에 들어가, 자신의 .NET Framework 버전에 맞추어 들어가면, log4net.dll 이 있는데, 이 파일만 꺼내오면 된다.

이제 자신의 Project 내에 저 파일을 복사( 개인적으로는 Solution 폴더 바로 아래에 Assembly 폴더를 만들어서 그 안에 복사) 해준다.

이제 자신의 프로젝트에 참조를 건다.

참조 건 뒤에 빌드 할 때 마다 파일이 복사되서 전달 될 수 있도록 Copy Local 이 True인지도 살펴본다.
(간혹 이 부분을 누락해서, 배포할 때, DLL이 빠져서 실제 이 배포본을 받는 사람은 실행이 안되는 문제가 발생하기도 한다.)

그리고 로그를 쌓는 로직을 작성한다.

그리고 이제 소스 상에서 다음과 같이 추가한다.

여기서는 제일 만만한 Form1.cs 파일을 열고 수정한다.

최초로 열면 아래와 같은 소스이다.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace MyTestProject
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    }
}

이제 이 소스를 다음과 같이 수정한다.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace MyTestProject
{
    public partial class Form1 : Form
    {
        private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
        public Form1()
        {
            log.Info("Form Init Start");    
            InitializeComponent();
            log.Info("Form Init End");
        }
    }
}

이제 프로젝트를 실행해보자.

분명 로그를 쌓으라고는 했으나, 로그가 쌓이지는 않는다. 당연하다.
로그를 쌓기 위한 log 개체는 만들었다.

private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

하지만, 위의 개체에다 아무리 로그를 쌓으라고 해도 어디다가 어떻게 쌓아야 될지에 대한 설정이 없으므로 쌓일리 없다.
즉 로그가 모두 무시되는 상황. 이를 위해서 설정을 해주어야 한다.

 

이 설정 방법은 크게 두 가지가 있는데, 하나는 정적인 방법이고, 다른 하나는 동적인 방법이다.

그 방법이 아래와 같다.

 

로그 쌓기 설정 그 방법 1.

구글을 통해 이런 저런 설정 관련 예제를 받으면 대부분은 .config 파일에다 그 설정을 넣는 방법을 제공한다.
즉 응용 프로그램 프로젝트의 경우 App.config 파일이 그 해당 파일인데 응용 프로그램 프로젝트를 최초로 만들 때는 없는 경우가 많다. 그 경우 새 항목을 추가해서 새로 만들어 주도록 한다.

이제 App.config 파일을 열어보면, 설정을 위한 XML 파일이 열린다.

<?xml version="1.0" encoding="utf-8" ?>
<configuration?> 
</configuration?>

이 안을 다음과 같이 설정한다.

<?xml version="1.0" encoding="utf-8" ?>
<configuration?> 
  <configsections?>
    <section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net" /?>
  </configsections?>
  <log4net?>
    <appender name="RollingFile" type="log4net.Appender.RollingFileAppender"?>
      <file value="example.log" /?>
      <appendtofile value="true" /?>
      <maximumfilesize value="100KB" /?>
      <maxsizerollbackups value="2" /?>
      <layout type="log4net.Layout.PatternLayout"?>
        <conversionpattern value="%level %thread %logger - %message%newline" /?>
      </layout?>
    </appender?>
    <!-- Set root logger level to DEBUG and its only appender to A1 --?>
    <root?>
      <level value="INFO" /?>
      <appender-ref ref="RollingFile" /?>
    </root?>
  </log4net?>
</configuration?>

그리고 Properties 폴더를 열고, AssemblyInfo.cs 파일을 연다.

이제 맨 아래 줄에다가, 다음 줄을 삽입한다.

[assembly: log4net.Config.XmlConfigurator(Watch = true)]

앞에서 로그 쌓는 로직이 정상적으로 작성되어 있다면, 이제 다시 프로젝트를 실행해보자.

실행 한 뒤에 Build 결과물 폴더를 열어보면 example.log 라는 파일이 생성되는데 그 안에 보면,

Form Init Start와 Form Init End가 보일 것이다.

설정이야 어쨌던 로그가 쌓인다.

 

로그 쌓기 그 방법 2

대부분의 경우는 위의 경우에 다 처리 될 수 있다. (상세 설정은 추가적인 검색을 하면 상세한 설정이 가능하다.)
그런데, 이런 저런 프로젝트에 적용하다가 보니, 이 로그 설정을 동적으로 설정해야 할 경우가 발생했다.

즉 app.config를 이용해서 설정하는 방식으로는 그 설정 내용을 마음대로 주무르기가 무척 힘들다는 사실이다.
예를 들면 로그 파일의 이름이나, 경로 같은 경우다. 경로가 딱 정해진 위치라면 상관 없지만, 프로그램이 실행된 뒤, 동적으로 변경되어야 하는 경우라면 이야기가 많이 달라진다.

예를 들면 Application 관련 설정이 다른 위치에 저장되고, 설정 창을 통해 로그 파일의 위치가 변경되는 경우라면 어떻게 할 것인가? 그럼 매번 app.config를 변경해서 재실행해야 할까?

이런 저런 생각에 동적(코드상)으로 변경하는 방식을 찾아보았고, 그 방법을 정리해 보았다.

 

먼저 동적인 방법으로 설정을 하기 위해서는 log 개체에 대한 구성체계를 가져와야 한다.

	log4net.Repository.Hierarchy.Hierarchy hierarchy = (log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetRepository();
	hierarchy.Configured = true;

 

이제 모든 설정은 hierarchy를 통해서 진행되게 된다.

먼저 제일 중요한 로직이 어디다가 어떻게 쌓을지에 대해서다.
log4net 에서 제공되는 쌓는 방식은 19가지 정도 되지만, 위의 예제도 그렇듯이, 파일로 제공하는 형태로 구성할 예정이다. 방법 1과 동일한 방식으로 쌓으려면 아래와 같은 로직을 통해 쌓는 방식에 대한 클래스를 생성한다.

	log4net.Appender.RollingFileAppender rollingAppender = new log4net.Appender.RollingFileAppender();
	rollingAppender.File = "C:\Temp\Test.log"; // 전체 경로에 생성할 메인 로그 파일 이름
	rollingAppender.AppendToFile = true;
	rollingAppender.RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Date;
	rollingAppender.LockingModel = new log4net.Appender.FileAppender.MinimalLock();
	rollingAppender.DatePattern = "_yyyyMMdd\".log\""; // 날짜가 지나간 경우 이전 로그에 붙을 이름 구성
	log4net.Layout.PatternLayout layout = new log4net.Layout.PatternLayout("%date [%property{buildversion}] %-5level %logger - %message%newline");
	rollingAppender.Layout = layout;

여기에 있는 RollongFileAppender는 log 내용을 파일로 쌓는 역할을 제공하는 모듈이다.
이 모듈에 대한 개체를 생성했으면, 이제는 hierarchy에 붙이도록 한다.

	hierarchy.Root.AddAppender(rollingAppender);
	rollingAppender.ActivateOptions();

이제 로그를 쌓는 레벨을 설정한다.

hierarchy.Root.Level = log4net.Core.Level.All;

이 로직을 로그가 쌓이기 전에 한번 실행 할 수 있도록 구성하면 된다. 다음 코드는 위의 부분 부분을 모두 합친 부분이다.

	log4net.Repository.Hierarchy.Hierarchy hierarchy = (log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetRepository();
	hierarchy.Configured = true;

	log4net.Appender.RollingFileAppender rollingAppender = new log4net.Appender.RollingFileAppender();
	rollingAppender.File = "C:\Temp\Test.log"; // 전체 경로에 생성할 메인 로그 파일 이름
	rollingAppender.AppendToFile = true;
	rollingAppender.RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Date;
	rollingAppender.LockingModel = new log4net.Appender.FileAppender.MinimalLock();
	rollingAppender.DatePattern = "_yyyyMMdd\".log\""; // 날짜가 지나간 경우 이전 로그에 붙을 이름 구성
	log4net.Layout.PatternLayout layout = new log4net.Layout.PatternLayout("%date [%property{buildversion}] %-5level %logger - %message%newline");
	rollingAppender.Layout = layout;

	hierarchy.Root.AddAppender(rollingAppender);
	rollingAppender.ActivateOptions();
	
	hierarchy.Root.Level = log4net.Core.Level.All;

프로그램 제일 먼저 시작하는 로직에 추가하면 된다. 대개의 경우에는 program.cs 파일내 추가해주면 된다.

        static void Main()
        {
            #region 로그 설정
            log4net.Repository.Hierarchy.Hierarchy hierarchy = (log4net.Repository.Hierarchy.Hierarchy)log4net.LogManager.GetRepository();
            hierarchy.Configured = true;

            log4net.Appender.RollingFileAppender rollingAppender = new log4net.Appender.RollingFileAppender();
            rollingAppender.File = "C:\Temp\Test.log"; // 전체 경로에 생성할 메인 로그 파일 이름
            rollingAppender.AppendToFile = true;
            rollingAppender.RollingStyle = log4net.Appender.RollingFileAppender.RollingMode.Date;
            rollingAppender.LockingModel = new log4net.Appender.FileAppender.MinimalLock();
            rollingAppender.DatePattern = "_yyyyMMdd\".log\""; // 날짜가 지나간 경우 이전 로그에 붙을 이름 구성
            log4net.Layout.PatternLayout layout = new log4net.Layout.PatternLayout("%date [%property{buildversion}] %-5level %logger - %message%newline");
            rollingAppender.Layout = layout;

            hierarchy.Root.AddAppender(rollingAppender);
            rollingAppender.ActivateOptions();
	
            hierarchy.Root.Level = log4net.Core.Level.All;
            #endregion


            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }

이제 app.config 없이도 log가 쌓이게 된다.

 

정리

이 포스트의 목적은 바로 이 2번째 방법에 따른 방식을 소개하기 위한 글이다.

log4net의 기능의 강력함은 동작 중에 무시할 만큼의 리소스를 소모하면서 강력하게 로그를 쌓는 방식에 있을 것이다. 더욱이 자동으로 파일로 떨구기도 하고, 데이터베이스에 쌓을 수도 있기 때문에, 활용도에서도 우수하다.

개인적으로 현재 프로젝트를 모두 이 log4net 으로 전환하고, 쌓이는 로그를 WinTail(http://www.baremetalsoft.com/wintail/)  이라는 프로그램을 통해서 살펴보고 있다. 동작 중에 발생되는 각종 값에 대한 감시 및 데이터 체크는 모두 이를 통해서 하고 있다. 동적으로 프로그램을 실행하면서 디버깅을 할 때 의외로 편하다.

닷넷 프로그램으로 로깅을 남기는 작업을 해보고 있다면 한번 시도해보심이?

728x90

요즘 C#을 통해서 Application을 만드는 중인데, 데이터 처리해야 될 내용이 많아, Excel과 Access를 주로 사용하고 있다. 특히 Excel의 경우에는 직접 COM 개체를 활성화 시켜서 Cell 단위까지 찾아 데이터를 가져오는 방법보다,
마치 DB Access 한 것 처럼 OLEDB로 연결해서 가져오는 방법이 가장 효율적이였던 것 같다.

그런데, 문제는 설치 대상 PC안에 오피스 제품이 무엇이 깔렸는지에 따라 이 OLEDB를 사용할 수 있는 것이 전혀 다르다. 특히 2013 이후에는 x64 전용이 있어, 응용 프로그램이 x64에서 동작하게 되면 OLEDB 32bit 버전은 접근이 불가능했다. 그래서 매번 어떤 오피스가 깔렸는지, 32bit, 64bit 구분을 하기에는 너무 로직이 복잡해졌다.

그래서 구글링으로 찾아 다양한 테스트 후 적용했는데 지금은 큰 문제가 없어 보여서 블로깅을 시작한다.

 

문제

오피스 데이터를 DB 처럼 접근하기 위해서는 그에 맞는 Connection String을 구성해야 한다.

MS Access 같은 경우에는,

"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\mydatabase.mdb;User Id=admin; Password=;"

라고 쓰고

MS Excel 같은 경우에는

"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\MyExcel.xls; Extended Properties="Excel 8.0;HDR=Yes;IMEX=1";"

라고 쓴다.

그런데, 문제는 저 Provider다.
현재 설치되어 있는 오피스 버전에 따라 Microsoft.Jet.OLEDB.4.0 을 쓸수도 있고,
때론 Microsoft.ACE.OLEDB.12.0 라고 쓰기도 하고,
x64 Office 2013의 경우에는 Microsoft.ACE.OLEDB.15.0 라고 해야 동작한다.
그 이유는 설치한 Office 마다 설치되는 OLEDB Provider가 달라서 벌어지는 일이다.

일반적으로 Office 2000, XP, 2003 까지는 Microsoft.Jet.OLEDB.4.0 를,
Office 2007, 2010의 경우에는 Microsoft.ACE.OLEDB.12.0 를
Office 2013의 경우에는 Microsoft.ACE.OLEDB.15.0를 사용하게 된다.

그렇다면 이 Provider가 어떻게 설치되어 있는지 알 수 있을까?
맨 처음, 적용했던 방식은 Registry를 통한 방식이였다.
Registry 내에 설치되어 있는 Provider가 있는지 검색하는 방법인데,
이 방법으로 했을때는 그럭저럭 잘 동작 했다.

그런데, 왠걸... x64로 넘어가자 문제가 발생했다.
분명 해당 Provider는 설치되어 있었지만,
내 프로그램에서 해당 Provider를 찾지 못하는 문제가 발생했다.
즉 x86일 때와 x64일 때 제공되는 OLE DB Provider가 다르다는 사실을 그 때 처음 알았다.

결국 원점으로 돌아왔고, 근본적인 방법을 찾아야 겠다는 생각이 들어 수정하기 시작했다.

 

해결

구글링을 통해서 다양한 웹페이지들을 들락달락해 본 결과 다음과 같은 예제 소스를 볼 수 있었다.
대게의 경우 앞서 언급한 특정 레지스트릐의 값을 통해 등록되어 있는지 여부를 찾는 것이였는데,
애석하게도 실패를 맞봤기 대문에, 실질적인 방법이 필요했다.그러다가 단서를 하나 발견했다.

http://stackoverflow.com/questions/6570066/c-net-get-the-oledb-provider-version

내용을 대충 훝어 본 후, 적절한 예제로 담긴 MSDN 링크를 따라 들어갔다.

http://msdn.microsoft.com/en-us/library/system.data.oledb.oledbenumerator.getelements.aspx

OleDbEnumerator.GetElements 라는 메소드를 사용해보라는 것이였다.
예제의 내용은 아래와 같다.

using System;
using System.Data;
using System.Data.OleDb;

class Program
{
    static void Main()
    {
        OleDbEnumerator enumerator = new OleDbEnumerator();
        DataTable table = enumerator.GetElements();
        DisplayData(table);
        Console.WriteLine("Press any key to continue.");
        Console.ReadKey();
    }

    static void DisplayData(DataTable table)
    {
        foreach (DataRow row in table.Rows)
        {
            foreach (DataColumn col in table.Columns)
            {
                Console.WriteLine(" = ", col.ColumnName, row[col]);
            }
            Console.WriteLine("==================================");
        }
    }
}

즉 OleDbEnumerator 라는 개체를 만들고 그 메소드 중, GetElements 라는 메소드를 호출하면 현재 프로그램 안에서
사용가능한 모든 Provider를 담은 정보를 DataTable 형식으로 돌려주게 된다.

여기서 착안하여, 실제 Provider들 중, 최신 Provider 이름을 추출하도록 했다.

OleDbEnumerator enumerator = new OleDbEnumerator();
DataTable table = enumerator.GetElements();
List aryProviders = new List();
foreach (DataRow row in table.Rows)
{
    aryProviders.Add(row[0].ToString());
}
table.Dispose();
aryProviders.Sort((p, n) => n.CompareTo(p));
string sFoundString = aryProviders.Find(m => m.StartsWith("Microsoft.ACE.OLEDB"));
if (string.IsNullOrEmpty(sFoundString))
    sFoundString = aryProviders.Find(m => m.StartsWith("Microsoft.Jet.OLEDB"));
g_sProviderName = sFoundString;

 

최종적으로 사용되는 값은 g_sProviderName 이라는 값으로, 이 값을 Connection String에 대입하기만 하면 된다.

string sConnectionString = string.Format("Provider=;Data Source=C:\MyExcel.xls; Extended Properties="Excel 8.0; HDR = Yes; IMEX = 1";", g_sProvider);

라고 하면 끝. 그러면 해당 Provider로 연결하게 된다.

Provider 검색하는 코드를 간단하게 설명하자면,  아래와 같다.

  1. 현재 가져올 수 있는 모든 Provider 정보를 DataTable로 가져온다.
  2. 가져온 Provider에서 Provider 이름만 모두 가져와 string array로 만든다.
  3. string array를 이름 별로 Sort 한다. - 최신 버전이 위로 갈 수 있도록 -
  4. 돌면서 최신 버전에 해당하는 것이 있는지 체크하면서 찾으면 해당 이름을 저장한다.
  5. Connection String을 만들 때, 저장한 Provider 이름을 사용한다.

간단하면서도 잘 알지 못하면, 참 어렵게 프로그램을 짤 번했던 기억이 든다.

참고로 Excel의 경우 "xsl" 파일은 Microsoft.Jet.OLEDB.4.0 만으로도 읽을 수 있지만, "xslx" 파일은 반드시 "Microsoft.ACE.OLEDB.12.0" 이상 버전의 Provider를 사용해야만 읽을 수 있다.

즉 위에서 필터링하는 방식에 따라, xlsx 파일을 읽을 수 있는지 없는지를 체크하는 방편이 될 수 있다.

728x90

Open Source기반의 EDM을 찾다가, 발견한 제품인 Alfresco. Java로 된 솔루션인데, 이미 상용화 서비스를 제공하고 있어서 인지, 제품의 완성도가 상당히 높았다.
그래서 이 Aflresco를 Windows에 설치 한 뒤, 다양하게 사용해 봤는데, 괜찮았다. 그래서 아예 서버를 분리해서 Alfresco 전용 서버를 구축하기로 마음먹고, 성능 향상을 위해 x64기반의 Ubuntu Server 위 에 설치했다.

그리고 현재 잘 사용하고 있다.

 

그런데, 예전에 설치해서 사용할 때는 Office 계열 문서의 경우 자체적으로 PDF로 변환을 해서, 이를 웹상에 바로 노출되었는데, 이게 제대로 동작하지 않고 있다. 즉, 진짜 바이너리 파일 처럼 문서가 계속 Word, Excel 등으로만 뜨고, Preview 화면이 제대로 표시되지 않는 문제가 계속 되었다. 처음에는 변환에 시간이 걸려서 그런가 했는데, 애석하게도 그렇지는 않은 듯 싶다.

 

구글링을 해보니 이런 글(http://www.simonbuckle.com/2007/06/06/converting-to-pdf-with-alfresco/) 이 걸렸다.

" lsof -i | grep 8100 " 라고 입력하면 Office 파일 변환 서비스가 떠 있어야 된다는 의미인데, 애석하게도 실행해본 결과 아무것도 나타나지 않았다. 결국 이 Office 파일 변환 서비스가 제대로 떠있지 않다라는 결론이 나고, 좌절을 맛볼 수 밖에 없었다.

 

마음을 다 잡고 다시 차근 차근 구글링을 시도했고, 이 문제를 해결 하기 위한 답을 찾았다.
이 문제를 해결 하기 위해 아래의 링크들을 참고했다.
https://forums.alfresco.com/forum/end-user-discussions/alfresco-share/cannot-preview-ms-office-files-03122012-1400

https://forums.alfresco.com/forum/installation-upgrades-configuration-integration/configuration/document-preview-broken-42b#p139371

 

문제 현상.

문제가 된 현상은 다음과 같다.


보면 모든 문서들이 단순 아이콘으로만 뜨고 있다. 더욱이 해당 문서 안으로 들어가면, 내용을 보여주는 Preview 화면 조차 없다.

 

그러다가, Tomcat 내부에 있는 catalina.out 이라는 로그 파일을 관찰했고, 문서를 읽을 때마다, 다음과 같은 오류를 뱉는 것을 확인했다.

ERROR [extensions.webscripts.AbstractRuntime] [http-8080-39] Exception from executeScript 
- redirecting to status template error: 02120007
The content node was not specified so the content cannot be streamed to the client:
classpath*:alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.js org.springframework.extensions.webscripts.WebScriptException: 02120007
The content node was not specified so the content cannot be streamed to the client:
classpath*:alfresco/templates/webscripts/org/alfresco/repository/thumbnail/thumbnail.get.js at org.alfresco.repo.web.scripts.content.StreamContent.execute(StreamContent.java:183) at org.alfresco.repo.web.scripts.RepositoryContainer$2.execute(RepositoryContainer.java:400)

그래서

Exception from executeScript - redirecting to status template error

로 검색을 했고, 위의 문제 해결 링크를 찾았다.

 

 

문제 분석

최초로 찾은 글에서는 몇몇 답변들이 있었지만, 별 의미는 없었다. 하지만, 맨 끝자락에 있는 "TTownsend"라는 분이 글을 남겼고, 그 분이 문제 해결에 대한 Thread를 남겼는데, 그게 결정적이였다. ( Thanks TTownsend! )

 

같은 문제인지 확인해 보려면, /alfresco/libreoffice/scripts Directory로 이동 한뒤, openoffice_ctl.sh 를 실행해 본다. 그러면 콘솔 창에 "openoffice did not start" 라는 메시지가 뜨면 바로 이 해결 방법으로 처리할 수 있는 문제이다.

 

그러면 이제 openoffice_ctl.sh 가 어떻게 실행되는지 확인한다.

 

먼저 openoffice_ctl.sh 파일을 열고 그 안에서 SOFFICE로 시작되는 모든 변수 값을 가져온다.


소스의 맨 윗부분인데, 이 내용을 메모장 같은 곳에 복사해서 각 변수들 값을 조합하여 직접 실행 시킬 수 있는 명령 줄을 만든다.

#!/bin/sh

# Open Office
SOFFICE_PATH="/alfresco/libreoffice/program"
SOFFICE_PORT="8100"
SOFFICEBIN=/alfresco/libreoffice/program/soffice.bin
SOFFICE="$SOFFICEBIN --nofirststartwizard --nologo --headless --accept=socket,host=localhost,port=$SOFFICE_PORT;urp;StarOffice.ServiceManager"
SOFFICE_STATUS=""
이 내용을
/alfresco/libreoffice/program/soffice.bin --nofirststartwizard --nologo --headless --accept=socket,host=localhost,port=8100;urp;StarOffice.ServiceManager
로 만든다.

 

명령줄이 준비되었으면 실행해본다.

root@testserver:/alfresco/libreoffice/scripts# /alfresco/libreoffice/program/soffice.bin --nofirststartwizard --nologo --headless --accept=socket,host=localhost,port=8100;urp;StarOffice.ServiceManager
/alfresco/libreoffice/program/soffice.bin: error while loading shared libraries: libXrender.so.1: cannot open shared object file: No such file or directory
No command 'urp' found, did you mean:
 Command 'unp' from package 'unp' (universe)
 Command 'burp' from package 'burp' (universe)
 Command 'arp' from package 'net-tools' (main)
 Command 'rup' from package 'rstat-client' (universe)
urp: command not found
StarOffice.ServiceManager: command not found
root@docsvr:/alfresco/libreoffice/scripts# apt-file search libXrender.so.1
libxrender1: /usr/lib/x86_64-linux-gnu/libXrender.so.1
libxrender1: /usr/lib/x86_64-linux-gnu/libXrender.so.1.3.0
libxrender1-dbg: /usr/lib/debug/usr/lib/x86_64-linux-gnu/libXrender.so.1.3.0

결론을 내자면, 이 문제의 모든 원인은 바로 Open Office 를 실행하기 위한 필수 라이브러리가 제대로 설치되어 있지 않아 발생되는 문제였다. Open Office 자체가 X11과 같은 X-Windows용 프로그램이다 보니, Linux Server와 최소 설치형태로 제공되는 시스템은 당연하게 X-Windows 라이브러리가 없었고, 그 결과 Open Office 프로그램을 실행할 수 없는 문제가 발생하는 것이다.

 

서버 내에 X-Windows 라이브러리가 없어서 발생되는 문제였으므로, 이 문제를 해결 하려면, 역시 X-Windows를 설치하면 해결 된다. 하지만, 고작 이 Open Office 서비스를 실행하기 위해서 X-Windows를 설치하는 것은 배보다 배꼽이 큰 결과로 봐도 무방한다. GUI 없이 시스템 자원을 확실하게 사용하려고 서버를 깔았는데 말이다.

 

그러므로, 거대한 X-Windows 구성요소 중, Open Office가 실행될 수 있는 최소 필수 라이브러리만 설치하도록 한다.

 

이를 하기 위해서는 준비 작업을 먼저 해야 한다.

 

 

준비 작업

특별히 준비할 것은 없지만, 현재의 문제를 정확히 파악하려면, 패키지 관리 도구를 업데이트 해야 한다.

문제 분석은 현재 설치되어 있는 라이브러리들을 파악해야 한다. 그 라이브러리는 어떤 패키지에 속해 있는지를 알아야 하는데, 그 작업을 하기 위해서는 apt-file 이라는 패키지가 필요하다. (이미 구성되어 있을 수도 있다.)

apt-get install apt-file

apt-file update
먼저 apt-file 패키지를 설치한다. 그리고 apt-file을 업데이트하도록 한다.

그러면 문제의 파일이 어느 라이브러리에 속하는지 알 수 있게 된다.

 

 

해결 방법

앞서 실행해 봤던 명령 줄을 실행한다.

그러면 아래처럼 결과가 나오는데, 그 중 굵게 표시한 부분을 주목하자. libXrender.so.1 이라는 라이브러리를 읽어오는데 문제가 발생했다는 것이다.
/alfresco/libreoffice/program/soffice.bin: error while loading shared libraries: libXrender.so.1: cannot open shared object file: No such file or directory
No command 'urp' found, did you mean:
No command 'urp' found, did you mean:
 Command 'unp' from package 'unp' (universe)
 Command 'burp' from package 'burp' (universe)
 Command 'arp' from package 'net-tools' (main)
 Command 'rup' from package 'rstat-client' (universe)
urp: command not found
StarOffice.ServiceManager: command not found
root@docsvr:/alfresco/libreoffice/scripts# apt-file search libXrender.so.1
libxrender1: /usr/lib/x86_64-linux-gnu/libXrender.so.1
libxrender1: /usr/lib/x86_64-linux-gnu/libXrender.so.1.3.0
libxrender1-dbg: /usr/lib/debug/usr/lib/x86_64-linux-gnu/libXrender.so.1.3.0

문제의 라이브러리가 속한 패키지를 파악한다.

apt-file search libXrender.so.1

라고 입력한다. 그러면 다음과 같은 결과를 돌려준다.

libxrender1: /usr/lib/x86_64-linux-gnu/libXrender.so.1 
ibxrender1: /usr/lib/x86_64-linux-gnu/libXrender.so.1.3.0
libxrender1-dbg: /usr/lib/debug/usr/lib/x86_64-linux-gnu/libXrender.so.1.3.0
결과를 보면 libxrender1 이라는 패키지에 구성된 파일임을 알 수 있다.

 

이 패키지를 다시 설치한다.

 apt-get install libxrender1
그러면 패키지와 연계되어 아직 설치되지 않았던 패키지들이 실행된다.
패키지 목록을 읽는 중입니다... 완료
의존성 트리를 만드는 중입니다
상태 정보를 읽는 중입니다... 완료
다음 새 패키지를 설치할 것입니다:
  libxrender1
0개 업그레이드, 1개 새로 설치, 0개 제거 및 22개 업그레이드 안 함.
20.9 k바이트 아카이브를 받아야 합니다.
이 작업 후 88.1 k바이트의 디스크 공간을 더 사용하게 됩니다.
받기:1 http://kr.archive.ubuntu.com/ubuntu/ raring-updates/main libxrender1 amd64 1:0.9.7-1ubuntu0.13.04.1 [20.9 kB]
내려받기 20.9 k바이트, 소요시간 1초 (13.5 k바이트/초)
Selecting previously unselected package libxrender1:amd64.
(데이터베이스 읽는중 ...현재 83473개의 파일과 디렉터리가 설치되어 있습니다.)
libxrender1:amd64 패키지를 푸는 중입니다 (.../libxrender1_1%3a0.9.7-1ubuntu0.13.04.1_amd64.deb에서) ...
libxrender1:amd64 (1:0.9.7-1ubuntu0.13.04.1) 설정하는 중입니다 ...
libc-bin에 대한 트리거를 처리하는 중입니다 ...
ldconfig deferred processing now taking place

이 작업을 앞에서 Open Office를 실행하기 위한 명령 줄 "/alfresco/libreoffice/program/soffice.bin --nofirststartwizard --nologo --headless --accept=socket,host=localhost,port=8100;urp;StarOffice.ServiceManager"를 실행하면서 오류가 발생하지 않을 때 까지, apt-file search 하고, apt-get install 을 해서 찾는다.

 

그러면 어느 순간부터는 실행 했을 때, 더 이상 입력되지 않고, 무언가가 계속 실행된 상태가 유지되는 것을 볼 수 있다. 필자의 경우 아래와 같이 되고, 실행된 상태로 계속 유지 되었다.

root@testserver:/alfresco/libreoffice/scripts/alfresco/libreoffice/program/soffice.bin --nofirststartwizard --nologo --headless --accept=socket,host=localhost,port=8100;urp;StarOffice.ServiceManager
Fontconfig warning: "/alfresco/libreoffice/share/fonts/truetype/fc_local.conf", line 13: Having multiple  in  isn't supported and may not work as expected

이제 Ctrl + C로 빠져 나오고, ./openoffice_ctl.sh start 명령을 입력한다.

그러면, "./openoffice_ctl.sh : openoffice started at port 8100" 라는 메시지가 나오고 종료된다.

 

그러면 성공!

 

이제 alfresco 서버를 다시 시작하면 정상적으로 동작하는 것을 볼 수 있다.

 



 

결론

X-Windows 라이브러리와 같은 기초 동작 라이브러리만 잘 구성해서 추가해주면 아주 부드럽게 잘 동작한다. 문서 Preview도 잘 되고, 화면도 팍팍 표시된다. 문제는 단순했지만, 역시 잘 모르면, 해결하기 그렇게 쉽지는 않다. 하지만, 전세계를 대상으로 검색해보면, 나와 같은 고민에 빠진 사람들을 쉽게 찾아볼 수 있고, 또, 그 문제를 접근하고, 해결하는 방법들이 잘 나와 있어 얼마나 다행인지 모르겠다. (아직도 NavXX 마수에 빠지신 분은 이 해결 방법 그렇게 쉽게 찾지는 못 할 듯! ㅋ )

 

어쨌던, 이 솔루션 생각보다 훌륭하다. 상당히 안정적이며 빠르다.

물론 설정이나, 구성 방법은 그렇게 쉽지만은 않지만, 그래도 성능 하나 만으로 이 모든 문제를 상쇄할 만큼 훌륭한 제품인 것 같다. 개인적인 문서 / 사진 라이브러리 구축을 위해 사용하고 있지만, 작은 규모의 팀이나, 회사에서도 충분히 활용 할만 하다. ( 아 아직 한글은 지원하지 않는다. 현재 필자가 개인적으로 번역 작업을 해보고 있다. ㅋ )

 

( Alfresco 한글화 구성 사이트 : http://crowdin.net/project/alfresco/ko )

728x90

정확히 하면 MS Access 2003 포맷 기준 MDB의 한계다.

최대 Object의 크기가 2G의 한계를 가지다 보니, 2G를 넘기면 더 이상 데이터 처리를 하지 못한다.

 

완전히 최초 DB에서 아래와 같이 테이블을 하나 만들었을 때 기준으로 본다.

ID(long 4 byte) + id1(long 4 byte) + id2(long 4 byte) + id3(long 4 byte) + datevalue(text * 10 ) + seriesvalue (double 8 byte )....

위 처럼 보았을 때 하나의 레코드를 약, 1~2K 정도로 본다. 최소 단위가 4K니 4K씩 잡아도 된다.

 

여튼, 무한대로 레코드를 넣었는데, 그 결과는...

 

약 2천 3백만 개의 레코드를 넣었더니 결국 무너졌다.

파일 사이즈가 약 2G 정도 되어서 그렇게 된 듯...

 

 

누가 나에게 1억개 되는 레코드를 핸들링 했다고 자랑스럽게 말하길래 진짜? 하는 마음에 돌려봤다. 뭐 1/5 채우고 자빠진 격...

뻥도.. 알고 쳐야지.. 아무한테나 막 내지른다고 완성되는 것은 아닌듯..

 

비정형 DBMS로 하면 모를까, 최소한 MS Access로는 안된다.  쾅!!!!쾅!!!

728x90

+ Recent posts

728x90